Returning a struct with a trait as field

Hi everyone,
I try to create a struct with a new function that has a trait as field, to be more specific the default output.

pub struct MyTest<'a> {
    ignore_case: bool,
    output: &'a (dyn Write + 'a),
}

impl<'a> MyTest<'a> {
    pub fn new() -> Self {   
        MyTest {
            ignore_case: false,
            output: io::stdout().by_ref(),
        }
    }
}

Unfortunately, i get only the error cannot return value referencing temporary value and can't find any help in the docs. How can I achieve it, that I can set stdout as default and later on set for example a file as output?

Any help is appreciated.
Cheers snmed

If you see the error, it means that the borrowing of io::stdout() happens in the local context of the function and so cannot have the lifetime you want it to have , 'a, which necessarily outlives the function.
What you want is to store the trait object as Box<dyn Write>, initializing output as Box::new(io::stdout()).

2 Likes

Hi RedDocMD, thank you for you quick reply. I will try your suggestion. Is it best practice in rust to use box for use cases like mine? I come from C# and Go and therefore Rust feels for me still a little bit weird. :wink:

Well, for your particular use case, it seems it's enough to store something like Option<File>: if it is None, use stdout, else use the provided file object. You can also make your own enum, if you need more granularity or cases.

1 Like

It's not a question of "best practice". If you want to return a trait object "by value", you have to put it behind indirection, because it's dynamically-sized. So there is (currently) no way to return a dyn Trait truly by-value, i.e. without boxing it (or putting it inside another kind of owning pointer, e.g. Rc or Arc).

I want to address the fundamental misunderstanding here. When a struct holds a borrow, it expects that somewhere else, that value is owned. In languages like C# and Go, that's fine, because the language runtime can keep track of all the references and perform garbage collection. But Rust is not garbage-collected, so you have a few options:

  • Option 1: store as a value

This option does not apply to the trait object, but with sized types, this is usually the best approach, since the value is owned by the struct that owns it logically.

  • Option 2: store as a borrow

For people used to languages like Go, this is certainly a possibility. Storing a pointer to an interface in Go might seem similar to storing a borrow in Rust, but in reality, by storing a borrow in Rust, that does not make the runtime "keep alive" that borrow. Rust has great ownership semantics via lifetimes so your code will not compile, but it also will not do what you want. We can see an example of this in a language like C++:

// treat this like our "trait"
struct Trait {
  virtual void method() = 0;
  virtual ~Base() = 0; // best practice
};

// and this is a struct that implements the trait
struct Struct : public Trait {
  int some_member;
  void method() override { ... }
};

// here we store a reference to something that "implements" the trait
// (since we can't create an instance of Trait due to pure virtuals)
// note the indirection here as well. If we stored a value of Trait,
// we would not be able to store Struct in it, because they are
// different sizes.
struct ReferencesTrait {
  Trait& member;
};

// now we can create the ReferencesTrait with a Struct instance
ReferencesTrait create_references_trait() {
  Struct on_stack; // this only lives for the duration of the function
  // we create a reference to that
  // OK as long as we don't use this outside of the function
  ReferencesTrait ret{ on_stack };
  // oops, dangling reference!
  return ret;
}

(Note: Your C++ compiler might give a warning about this, but will not refuse to compile.)

Rust prevents this at compile time. ReferencesTrait would require a lifetime parameter, and on_stack would not live long enough to return it from create_references_trait.

So how can we solve this? We need to avoid storing the struct implementing the trait on the stack. So let's store it on the heap!

  • Option 3: store in a Box<dyn Trait>

This is the most common method for owned trait objects. By heap-allocating the trait object, it is still behind a layer of indirection (Box stores a pointer), but we are able to own it inside our ReferencesTrait struct because the pointer itself is sized. However, what if we want to replicate the ability in C# and Go to store the same object in multiple structs?

  • Option 4: Rc (or Arc), and maybe some interior mutability

I have tried a lot to share references between multiple structs, but the bottom line is that Rc or Arc make it so much easier. No pesky lifetime parameters everywhere! Simply put, Rc is a form of garbage collection called reference counting, used in languages such as Python. It ensures that, as long as anyone has a reference to the internal value, the value will continue to exist.

Of course, by having multiple references to the same value, it would have to only allow immutable access, which is where interior mutability types like Mutex, RefCell, Atomic* come in. But that's a bit off-topic.

Recap

Basically, when a type is not sized, you have two options: store it as a borrow, or as an owned pointer (Box, Rc, Arc). These simply depend on your use case, but I find owned pointers to be easier to deal with in general.

2 Likes

Thanks a lot for clarification.

@mattfbacon Thank you very much for your verbose and ample elaboration.