2

I have a reference to a struct.

I need a reference to a struct. Sometimes it's the same, but sometimes it's a cloned then modified one.

Right now, I do

if condition {
    let mut copy = original.clone();
    copy.select();
    do_big_thing(&copy);
} else {
    do_big_thing(original);
}

But do_big_thing takes other arguments, I don't like to duplicate this line.

Is there a way to

  • first build the reference, which would either be the original one or a reference to a local modified clone
  • then use it in do_big_thing (it doesn't have to live longer)

?

Of course this doesn't work because copy doesn't live long enough:

let mut reference = if condition {
    let mut copy = original.clone();
    copy.select();
    &copy
} else {
    original
};
do_big_thing(reference);

As this question is about finding a cleaner and less cluttered way to write the same thing (either use the original reference or clone), I can't accept a solution which would add overhead at runtime or some unsafe.

playground

Denys Séguret
  • 372,613
  • 87
  • 782
  • 758
  • You only need a trivial change to your code: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=ba63bddf6ad7ea9476b09e48b01c54d1 – Sven Marnach Jul 31 '19 at 14:45
  • @SvenMarnach that's shep's answer. And yes it's trivial but I didn't knew this was allowed – Denys Séguret Jul 31 '19 at 14:48
  • 1
    There are two good answers here, both with zero cost thanks to compiler optimizations. I suggest future readers look at both answers. – Denys Séguret Jul 31 '19 at 15:00

2 Answers2

6

Since you don't intend to return the value from your function (which is shown in Is it possible to return either a borrowed or owned type in Rust?), you don't need to use Cow. Instead, you can declare the copy variable outside of the conditional:

fn do_huge_thing_stack(original: &Big, a: usize, condition: bool) {
    let mut copy;

    let r = if condition {
        copy = original.clone();
        copy.select();
        &copy
    } else {
        original
    };

    do_big_thing(r, a);
}

The compiler will ensure that a reference is only taken when the value is valid. This generates the same assembly as the Cow version, and I think the Cow version is more obvious, but it's still a useful technique to know.

See also:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • But... this is allowed ? This is really what I wanted to do but I didn't know it was possible to define a variable without value. – Denys Séguret Jul 31 '19 at 14:43
  • @DenysSéguret and note that this coding pattern doesn't require that `copy` be mutable. We only need `mut` because `select` requires it. – Shepmaster Jul 31 '19 at 14:51
3

You can use std::borrow::Cow to abstract over a value which is either borrowed or owned:

fn do_big_thing(b: Cow<Big>, a: usize) {
    println!("{}", a + b.c);
}

fn do_huge_thing(original: &Big, a: usize, condition: bool) {
    if condition {
        let mut copy = original.clone();
        copy.select();
        do_big_thing(Cow::Owned(copy), a);
    } else {
        do_big_thing(Cow::Borrowed(original), a);
    }
}

Which you can then refactor to something like:

pub fn do_huge_thing_cow(original: &Big, a: usize, condition: bool) {
    let mut r = Cow::Borrowed(original);

    if condition {
        r.to_mut().select();
    }

    do_big_thing(&r, a);
}

As you can see, this is optimized away.

Wesley Wiser
  • 9,491
  • 4
  • 50
  • 69
  • A cow is an enum. Instead of one test, we now have two. Or is the compiler able to simplify this ? – Denys Séguret Jul 31 '19 at 14:17
  • If there's reason to think the compiler can remove the second test, please that information part of the answer. – Denys Séguret Jul 31 '19 at 14:20
  • As I didn't trust too simple cases (which can be statically totally computed), I compared the ASM in [my code](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=21314881243dc538b75d46477b57e97b) and [the COW solution](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=2b16f498c3f401a6f1e233b868e03e23). The ASM is the same. So it looks like the compiler is really able to solve this case. – Denys Séguret Jul 31 '19 at 14:39
  • 1
    @WesleyWiser Zero cost abstractions is not about optimized code, it's about you don't pay more for a feature that what you would have pay by doing it at hand. Cow add a overhead if you use its features. However here we don't really use its feature but its logic, the compiler is able to see and remove the overhead but that not about zero cost, just about optimization, you can't expect the compiler will be able to do this on every CoW. But you can expect it when you don't actually use the feature of CoW like here – Stargateur Jul 31 '19 at 14:42
  • I think it's important to be clear that Cow isn't a ZCA in general (stargateur's comment is useful for that). But that it the cost is removed by the compiler's optimization (hence why it's a valid answer). – Denys Séguret Jul 31 '19 at 14:54
  • 2
    @DenysSéguret CoW is ZCA, but not "free" ;) – Stargateur Jul 31 '19 at 14:58
  • 1
    ZCA does not mean "compiles to zero assembly" it means it doesn't impose additional overhead than if you'd written it from scratch yourself. In Rust, enums, structs, and functions are generally ZCA's. As far as I can tell, `Cow` is a ZCA. Its implementation does exactly what you'd expect it to do and nothing more. @Stargateur can you provide an example where it is not a ZCA? – Wesley Wiser Jul 31 '19 at 15:02