Lifetime vs block scope: This should work, shouldn't it?

I am currently trying to refactor some of my rust code and have run into a problem which seems to be occurring far more often than I would like to admit. Let’s start with the code and than the explanation:

trait Foo {
    fn fizz(&self) {}
}

struct FooBar {}
impl Foo for FooBar {}

struct Bar {}
impl Foo for Bar {}

fn foo_bar() -> Result<FooBar, ()> {
    Ok(FooBar{})
}

fn create_fizz_buzz(id: String) {
    let fizz_buzz: &Foo;
    
    if id == "bar" {
        // Does not work
        fizz_buzz = match &foo_bar() {
            Ok(new_instance) => new_instance,
            Err(_) => panic!("Ahhh"),
        };
        
        // This would work but then you can't use a factory function which could fail.
        // fizz_buzz = &FooBar{};
    } else {
        fizz_buzz = &Bar{};
    }
    
    // Do some work with fizz_buzz.
}

fn main() {
    create_fizz_buzz("bar".to_string());
}

I am trying to create a object dynamically based on an identifier given to a function. Since all objects which could be created implement the same trait I can just create a match on the identifier. In this match I create the object and then assign it to a reference which expects the trait. Now I can use the reference to call functions on the reference. Sounds good.

However, once you try to use a factory function which can fail to create the objects, you can not assign the unpacked the objects to the reference variable due to their lifetime. (If you need to handle possible errors.) It compiles with the error

error[E0597]: borrowed value does not live long enough
  --> src/main.rs:20:28
   |
20 |         fizz_buzz = match &foo_bar() {
   |                            ^^^^^^^^^ temporary value does not live long enough
...
23 |         };
   |          - temporary value dropped here while still borrowed
...
32 | }
   | - temporary value needs to live until here
   |
   = note: consider using a `let` binding to increase its lifetime

Is there a way to do this correctly in Rust?
(I can do this in other programming languages, I mean, I am just trying to set a reference in a switch statement right? [Same problem applies to ifs by the way.])

You need an “anchor” for the struct instantiated by returning it from a function:

fn create_fizz_buzz(id: String) {
    let fbs;
    let fizz_buzz: &Foo;
    
    if id == "bar" {
        fbs = foo_bar().expect("Ahhh");
        fizz_buzz = &fbs;
    } else {
...
1 Like

Thank you very much.

So just to get this straight, if I have 100 possible objects to be instantiated, I need 100 variables of which I will use just one?

I think that there is a small misunderstanding. The example shown by @inejge works flawlessly for a simple reason: he is showing you that a owner is needed, in this case fbs;

I need to ask you a thing: why do you want to take a reference to the output of foo_bar()?

If you try to do this exact thing in C or C++, it compiles, but you will trigger an undefined behaviour. Here Rust is just helping you to avoid a good shoot in your feet :wink:

Just try to remove the ampersand from the foo_bar(), and see if things work as you expect.

I can’t remove the ampersand due to this line:

fn create_fizz_buzz(id: String) {
    let fizz_buzz: &Foo;
    ...
}

Since Foo is a trait, the size is unknown at compile time. Which in turn results in a compilation error. So creating an anchor is still the only solution?

If you wanted to keep that exact pattern, then yes, 100 separate variables would be needed.

However, at the cost of some boilerplate, you could consolidate all possible fn-instantiated structs in an enum, and have a single variable of that type:

struct Baz {}
impl Foo for Baz {}

enum Wrapper {
    FooBar(FooBar),
    Baz(Baz),
}

impl Wrapper {
    fn foo_obj(&self) -> &Foo {
        match *self {
            Wrapper::FooBar(ref s) => s,
            Wrapper::Baz(ref s) => s,
        }
    }
}

fn baz() -> Result<Baz, ()> {
    Ok(Baz {})
}

fn create_fizz_buzz(id: String) {
    let fbs;
    let fizz_buzz: &Foo;

    if id == "bar" {
        fbs = Wrapper::FooBar(foo_bar().expect("Ahhh"));
        fizz_buzz = fbs.foo_obj();
    } else if id == "baz" {
        fbs = Wrapper::Baz(baz().expect("Blech"));
        fizz_buzz = fbs.foo_obj();
    } else {
...

Sorry, I only focused to the reference, forgetting you wanted to use a dynamic trait.

Nevertheless, you still need an owner. @inejge showed how to do it using an Enum, another solution is to use a Box<dyn Foo>, which has the downside of heap allocating. However, with the Box you can handle as many structs implementing Foo you want.

Thank you guys!