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.
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 Output
s, 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With