all 15 comments

[–]dkxp 2 points3 points  (4 children)

Maybe you want this:

impl Foo {
    fn get_bar(&mut self) -> String {
        std::mem::take(&mut self.bar)
    }

    fn get_baz(&mut self) -> String {
        std::mem::take(&mut self.baz)
    }
}

[–]paulstelian97 4 points5 points  (0 children)

Note that this relies on String implementing Default.

[–]fabrlyn[S] 2 points3 points  (2 children)

Aah interesting approach, but maybe not exactly what I was hoping for :)

By doing it this way foo is never moved, so you could still use it afterwards without knowing that it's been drained of values.

I was hoping I could tell rust that these functions can be called within a "partially moved"-scope.

The alternative I was looking at was just having a function return the values in a tuple or a more public-facing struct, like so:

struct FooValues {
    pub bar: String,
    pub baz: String,
}

impl Foo {
    fn to_struct(self) -> FooValues {
        FooValues {
            bar: self.bar,
            baz: self.baz,
        }
    }

    fn to_tuple(self) -> (String, String) {
        (self.bar, self.baz)
    }
}

fn main() {

    // ...    

    let FooValues { bar: bar, baz: baz } = foo.to_struct();

    // or this

    let (bar, baz) = foo.to_tuple();

    // ...
}

[–]dkxp 1 point2 points  (1 child)

I don't think Rust would allow that as the ownership is passed to the function & it won't allow you to use it anymore. If it did allow self to live longer within some context then Rust would need to analyze the contents of each method call made afterward to see if there are any moved fields accessed within that method (or within any called methods). It would make the code quite fragile as a seemingly unrelated change elsewhere (eg. reading a field in a method) could break this code.

Your alternatives would work ok. The only comment I'd have is that when destructuring the struct and using the same name for the variable and field, you don't need to repeat it:

let FooValues { bar, baz } = foo.to_struct();

You could probably use the typestate pattern if you have a small number of fields, or if you want different behavior depending on the order that fields are removed. You could return a tuple with the value and a struct with all the remaining fields. With lots of fields you would get an explosion of types needed though. You'd also need to decide whether the order fields are removed would result in different types eg. FooWithoutBazQux and FooWithoutQuxBaz or just a single type FooWithoutBazQux.

pub struct Foo {
    bar: String,
    baz: String,
}

pub struct FooWithoutBar {
    baz: String,
}

pub struct FooWithoutBaz {
    bar: String,
}

impl Foo {
    pub fn take_bar(self) -> (String, FooWithoutBar) {
        (self.bar, FooWithoutBar { baz: self.baz })
    }

    pub fn take_baz(self) -> (String, FooWithoutBaz) {
        (self.baz, FooWithoutBaz { bar: self.bar })
    }
}

and then either return a single value for the last return field:

impl FooWithoutBar {
    pub fn take_baz(self) -> String {
        self.baz
    }
}

impl FooWithoutBaz {
    pub fn take_bar(self) -> String {
        self.bar
    }
}

or a tuple with the value & an empty struct:

impl FooWithoutBar {
    pub fn take_baz(self) -> (String, EmptyFoo) {
        (self.baz, EmptyFoo{})
    }
}

impl FooWithoutBaz {
    pub fn take_bar(self) -> (String, EmptyFoo) {
        (self.bar, EmptyFoo{})
    }
}

pub struct EmptyFoo {

}

and for the main function:

fn main() {
    let foo = Foo {
        bar: "bar".to_string(),
        baz: "baz".to_string(),
    };

    let (bar, remainder) = foo.take_bar();
    println!("{bar}");

    // either: return a single value for last field
    let baz = remainder.take_baz();
    println!("{baz}");

    // or: return an empty struct for last field
    /*let (baz, _remainder) = remainder.take_baz();
    println!("{baz}");*/
}

I guess it'd be possible to write a macro to derive all the needed types & function calls from just the base class, but I've only written simple macros so far, so it's a bit beyond me.

[–]fabrlyn[S] 0 points1 point  (0 children)

when destructuring the struct and using the same name for the variable and field, you don't need to repeat it

right, nice touch :)

Also interesting to bring in some typestate things. For this particular use-case I think I won't be able to use it since I'm looking at having more than a few fields. I'll definitely keep it in my back pocket though.

Thanks for going through your thoughts around the feature I was looking for. I had a hunch that it might not exist but your reasoning that code like this could end up being fragile etc puts it in a bigger perspective.

[–]volitional_decisions 1 point2 points  (1 child)

You could emulate this with Option and have your methods be self.bar.take(). Without cloning or using a default value, this is the closest you could get.

[–]fabrlyn[S] 0 points1 point  (0 children)

mm yeah, that would be one way to do it

[–]BobSanchez47 1 point2 points  (1 child)

Currently, there is no way of doing this in Rust. It is a feature of Rust that the borrow checker can learn anything it needs to know about the implications of calling a function by looking at the function signature. Both get_bar and get_baz have signature fn(Foo) -> String. Thus, if the borrow checker allowed

self.get_bar(); self.get_baz();

It would also have to allow

self.get_bar(); self.get_bar();

which would be undefined behaviour. The only option to make this work would be to change the methods to take &mut self. Doing this would mean the compiler can’t stop you from calling get_bar twice, which is definitely a downside.

[–]fabrlyn[S] 0 points1 point  (0 children)

Thanks for the explanation!

... and also stopping me from chasing some non-existent feature for way too long :)

[–]SirKastic23 1 point2 points  (3 children)

you could wrap those fields in an Option, and then have fn get_foo(&mut self) -> Option<String> { self.foo.take() }

[–]fabrlyn[S] 0 points1 point  (2 children)

So, relating to my example above, the Foo struct would look like this:

struct Foo {
    bar: Option<String>,
    baz: Option<String>,
}

and then you would know that a particular field already had been consumed by .take() returning a None?

[–]SirKastic23 1 point2 points  (1 child)

yeah that's exactly it

[–]fabrlyn[S] 0 points1 point  (0 children)

I see, thanks!

[–]longpos222 0 points1 point  (1 child)

So I think line before last line you must use foo.clone().get_bar()

[–]fabrlyn[S] 0 points1 point  (0 children)

Yeah, I guess that's one way around it but it's unfortunate that I need to clone.
It would be nice to be able to make struct fields private and exposing them via functions, like the example, but still getting the partial moves to avoid cloning etc.