Returning reference (ideas to improve)

Assume the following code:

trait VarTrait{
    fn get(&self) -> &str;
}

struct Value1(String);

impl VarTrait for Value1 {
    fn get(&self) -> &str {
        &self.0
    }
}

struct Value2(String);

impl VarTrait for Value2 {
    fn get(&self) -> &str {
        &format!("{}++", self.0)
    }
}
  1. This works fine for the first implementation but obviously fails at the second.
  2. Changing the trait to return a String will require a clone at the first implementation.
  3. Wrapping around a Box/Rc will also require a clone since those own the value.

I propose that the compiler should be smart enough to transform and accept the second implementation into something like:

impl VarTrait for Value2 {
    fn get(&self) -> (&str, String) {
        let companion = format!("{}++", self.0);
        (&companion, companion)
    }
}

I call this... reference with a companion owned value. The companion value is hidden and moves to outer scopes (never to inner scopes) following the upper lifetime of the reference. This will allow to return by reference and support symmetric capabilities (in, out), following the principle of least surprise. As also, it probably can open the path for some optimizations.

Using the same semantics will also help in situation such as:

let x = String::from("Value").as_bytes();
println!("OK: {:?}", x);

where the compiler complains about the temporary value.
And maybe it would also help to reduce the use of explicit lifetimes.
What do you think?

When you need to abstract over data that might be owned or immutably borrowed, you can use Cow to create a copy-on-write pointer:

use std::borrow::Cow;

trait VarTrait {
    fn get(&self) -> Cow<str>;
}

struct Value1(String);

impl VarTrait for Value1 {
    fn get(&self) -> Cow<str> {
        Cow::Borrowed(&self.0)
    }
}

struct Value2(String);

impl VarTrait for Value2 {
    fn get(&self) -> Cow<str> {
        Cow::Owned(format!("{}++", self.0))
    }
}

I prefer this to the idea of the language having a transformation 'baked in', as it makes it clear from the trait's interface that the returned value might not just be a cheap reference.

1 Like

That's fine, but the compiler doesn't actually need to create anything new. The tuple is just there to make my point. Where is the overhead?

Every creation of owned value (companion value, here) is potential overhead, due to the allocation. We usually don't want to make it implicit.

How do you expect your companion tuple to work? When you return the owned value it is moved, invalidating the reference.