Reputation: 23
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
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
}
}
Upvotes: 1