lemmington
lemmington

Reputation: 23

Trouble with using a trait with lifetime parameter as a generic bound

I am trying to use a trait Context whose different implementations allows Cell to access its owner World in different ways, as in the following contrived example code (which compiles; playground here):

struct Cell {    
    value: i32,    
} 
    
impl Cell {    
    fn evolve<'a>(&self, ctx: impl Context<'a>) -> Cell {    
        Cell {    
            value: self.value + ctx.get_rate()    
        }    
    }    
}    
    
struct World {    
    cell: Cell,    
    magic: i32,    
}    
    
impl World {    
    fn evolve_cell<'a, C: Context<'a>>(&'a self) -> Cell {    
        let ctx = C::new(self);        
        self.cell.evolve(ctx)        
    }    
        
    fn update(&mut self) {    
        self.cell = self.evolve_cell::<MagicContext>();    
    }    
}        
         
trait Context<'a> {    
    fn new(world: &'a World) -> Self;    
    fn get_rate(&self) -> i32;    
}    
     
struct MagicContext<'a> {    
    world: &'a World,        
}                            
                             
impl <'a> Context<'a> for MagicContext<'a> {                     
    fn new(world: &'a World) -> Self {    
        MagicContext {    
            world    
        }    
    }        
             
    fn get_rate(&self) -> i32 {    
        self.world.magic    
    }    
}        
         
fn main() {    
    let mut world = World {    
        cell: Cell { value: 2 },    
        magic: 3,    
    };    
          
    world.update();    
    println!("{}", world.cell.value);    
}

Instead of specifying MagicContext as the Context implementation in World::update, I would like to be able to choose the implementation at the call site in main -- something like this:

impl World {    
    // ...

    fn update<C: Context>(&mut self) {    
        self.cell = self.evolve_cell::<C>();    
    }
}

// ...

fn main() {
    // ...
    world.update::<MagicContext>();
    // ...
}

But Context needs a lifetime parameter. I can't introduce a lifetime parameter like fn update<'a, C: Context<'a>>(&mut self) since the context's lifetime will prevent me from mutating self:

error[E0506]: cannot assign to `self.cell` because it is borrowed
  --> lifetime.rs:25:9
   |
24 |     fn update<'a, C: Context<'a>>(&'a mut self) {
   |               -- lifetime `'a` defined here
25 |         self.cell = self.evolve_cell::<C>();
   |         ^^^^^^^^^^^^-----------------------
   |         |           |
   |         |           borrow of `self.cell` occurs here
   |         |           argument requires that `*self` is borrowed for `'a`
   |         assignment to borrowed `self.cell` occurs here

Some seemingly similar questions led me to have a look at Higher-Rank Trait Bounds, along the lines of fn update<C: for <'a> Context<'a>>(&mut self), but the compiler tells me that:

error: implementation of `Context` is not general enough
  --> lifetime.rs:56:11
   |
56 |     world.update::<MagicContext>();
   |           ^^^^^^ implementation of `Context` is not general enough
   |
   = note: `Context<'0>` would have to be implemented for the type `MagicContext<'_>`, for any lifetime `'0`...
   = note: ...but `Context<'1>` is actually implemented for the type `MagicContext<'1>`, for some specific lifetime `'1`

I would like to be able to get rid of Context's lifetime parameter altogether, but I don't know how to express the new function without it.

How can I achieve this (or something equivalent in a more idiomatic way)?

Upvotes: 2

Views: 102

Answers (1)

Chayim Friedman
Chayim Friedman

Reputation: 70880

This can be resolved using GATs (playground):

#![feature(generic_associated_types)]

struct Cell {
    value: i32,
}

impl Cell {
    fn evolve(&self, ctx: impl Context) -> Cell {
        Cell {
            value: self.value + ctx.get_rate(),
        }
    }
}

struct World {
    cell: Cell,
    magic: i32,
}

impl World {
    fn evolve_cell<C: Context>(&self) -> Cell {
        let ctx = C::new(self);
        self.cell.evolve(ctx)
    }

    fn update<C: Context>(&mut self) {
        self.cell = self.evolve_cell::<C>();
    }
}

trait Context {
    type Instance<'a>: Context;
    fn new<'a>(world: &'a World) -> Self::Instance<'a>;
    fn get_rate(&self) -> i32;
}

struct MagicContext<'a> {
    world: &'a World,
}

impl<'a> Context for MagicContext<'a> {
    type Instance<'b> = MagicContext<'b>;
    
    fn new<'b>(world: &'b World) -> Self::Instance<'b> {
        MagicContext { world }
    }

    fn get_rate(&self) -> i32 {
        self.world.magic
    }
}

Unfortunately, GATs are unstable. There is a workaround for lifetime GATs, though: create another trait with the associated type:

trait ContextInstance<'a> {
    type Instance: Context;
}
trait Context: for<'a> ContextInstance<'a> {
    fn new<'a>(world: &'a World) -> <Self as ContextInstance<'a>>::Instance;
    fn get_rate(&self) -> i32;
}

impl<'a, 'b> ContextInstance<'b> for MagicContext<'a> {
    type Instance = MagicContext<'b>;
}
impl<'a> Context for MagicContext<'a> {
    fn new<'b>(world: &'b World) -> <Self as ContextInstance<'b>>::Instance {
        MagicContext { world }
    }

    fn get_rate(&self) -> i32 {
        self.world.magic
    }
}

Playground.

Upvotes: 1

Related Questions