Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Specifying lifetimes when using the factory pattern in Rust

Tags:

rust

The following code doesn't compile:

trait Phone {
    fn call(&self);
}

struct IPhone<'a> {
    my_str: &'a str
}

impl<'a> Phone for IPhone<'a> {
    fn call(&self) {
        print!("{}", self.my_str);
    }
}

trait Factory<'a, P: Phone> {
    fn new_phone(&self, ms: &'a str) -> P;
}

struct IPhoneFactory;
impl<'a> Factory<'a, IPhone<'a>> for IPhoneFactory {
    fn new_phone(&self, ms: &'a str) -> IPhone<'a> {
        return IPhone {
            my_str: ms
        };
    }
}

fn call_phone<'a, P: Phone, F: Factory<'a, P>>(f: F) {
    for _ in 0..10 {
        let s = String::new();
        let p = f.new_phone(s.as_str());
        p.call();
    }
}

fn main() {
    call_phone(IPhoneFactory);
}

I get the following error:

error: `s` does not live long enough
        let p = f.new_phone(s.as_str());
                            ^
note: reference must be valid for the lifetime 'a as defined on the block at 28:53...
 fn call_phone<'a, P: Phone, F: Factory<'a, P>>(f: F) {
                                                      ^
note: ...but borrowed value is only valid for the block suffix following statement 0 at 30:30
        let s = String::new();
                              ^

I want to be able to have a factory that returns an abstract class, but when that class takes a reference I can't figure out how to specify the lifetime properly.

like image 722
Bob Bobbio Avatar asked Jul 15 '16 05:07

Bob Bobbio


1 Answers

You're right about that:

There is no reason for the reference to live as long as the factory, it only needs to live as long as the object the factory is creating (the factory itself doesn't store a reference to the string).

But the bound on call_phone says something different

fn call_phone<'a, P: Phone, F: Factory<'a, P>>(f: F) { ... }

That code says that there's a single lifetime for the whole factory, which will be used for each phone. You want something different, you want to say that f is a good factory for any lifetime:

fn call_phone<..., F: for<'a> Factory<'a, ...>>(f: F) { ... }

The other problem is that in the Factory trait definition:

trait Factory<'a, P: Phone> {
    fn new_phone(&self, ms: &'a str) -> P;
}

There's nothing tying lifetime of P to ms. The trait definition allows the returned phone to outlive the string, which should definitely be forbidden for the IPhone implementation! So, to fix it, we add a lifetime parameter to the Phone trait:

trait Phone<'a> {
    fn call(&self);
}

But there's still one problem. We can't really write that signature:

fn call_phone<P: ???, F: for<'a> Factory<'a, P<'a>>(f: F) { ... }

Since we want P to be not a type, but rather a family of types (more precisely, a lifetime → type constructor). Remember, the phone in each iteration of loop has a different type (since the lifetime is a part of a type, and lifetimes in different iterations of loops are different).

Ability to express such a signature is planned for the future Rust, but for now, we have to make a workaround and make the phone associated type of Factory trait:

trait Phone<'a> {
    fn call(&self);
}

struct IPhone<'a> {
    my_str: &'a str
}

impl<'a> Phone<'a> for IPhone<'a> {
    fn call(&self) {
        println!("{}", self.my_str);
    }
}

trait Factory<'a> {
    type Output: Phone<'a>;
    fn new_phone(&self, ms: &'a str) -> Self::Output;
}

struct IPhoneFactory;
impl<'a> Factory<'a> for IPhoneFactory {
    type Output = IPhone<'a>;
    fn new_phone(&self, ms: &'a str) -> IPhone<'a> {
        IPhone {
            my_str: ms
        }
    }
}

fn call_phone<F: for<'a> Factory<'a>>(f: F) {
    for i in 0..10 {
        let s = i.to_string();
        let p = f.new_phone(&s);
        p.call();
    }
}

fn main() {
    call_phone(IPhoneFactory);
}

Associated type allows the factory to produce only one kind of product, which is maybe what you wanted. If you want different implementations of Factory to have different Outputs, you can achieve this by using phantom types:

trait Phone<'a> {
    type Phantom;
    fn call(&self);
}

enum IPhonePhantom {}

struct IPhone<'a> {
    my_str: &'a str
}

impl<'a> Phone<'a> for IPhone<'a> {
    type Phantom = IPhonePhantom;
    fn call(&self) {
        println!("{}", self.my_str);
    }
}

trait Factory<'a, Selector> {
    type Output: Phone<'a, Phantom=Selector>;
    fn new_phone(&self, ms: &'a str) -> Self::Output;
}

struct MyFactory;
impl<'a> Factory<'a, IPhonePhantom> for MyFactory {
    type Output = IPhone<'a>;
    fn new_phone(&self, ms: &'a str) -> IPhone<'a> {
        IPhone {
            my_str: ms
        }
    }
}

fn call_phone<Selector, F: for<'a> Factory<'a, Selector>>(f: F) {
    for i in 0..10 {
        let s = i.to_string();
        let p = f.new_phone(&s);
        p.call();
    }
}

fn main() {
    call_phone::<IPhonePhantom, _>(MyFactory);
}

The Phantom associated type on the Phone trait is not strictly necessary, it's only needed to tie the phone type to its phantom type and to make sure Factory implementors don't lie.

like image 121
krdln Avatar answered Nov 15 '22 07:11

krdln