'Generic lifetime parameter and local scope
piet
is a drawing library with generic backends (cairo, for example).
I want to have a trait Renderable
that can render to any piet backend (=context).
pub trait Renderable {
fn render<C: piet::RenderContext>(&self, ctx: &mut C);
// Since `Renderable` can render to any `RenderContext`, it can render to a file too.
fn save_to_file(&self, path: &Path) {
let ctx = build_cairo_context_for_image_file(path);
self.render::<piet_common::CairoRenderContext>(&mut ctx);
}
}
However rustc complained about object unsafety of render<C>
, so I made the trait itself generic:
pub trait Renderable<C: piet::RenderContext> {
fn render(&self, ctx: &mut C);
fn save_to_file(&self, path: &Path) -> Result<(), piet_common::Error> {
let width = 512;
let height = 512;
let pix_scale = 1.0;
let mut device = piet_common::Device::new()?;
let mut bitmap: piet_common::BitmapTarget =
device.bitmap_target(width, height, pix_scale)?;
let mut rc = bitmap.render_context();
// Renderable::<CairoRenderContext>::render(self, &mut rc);
self.render(&mut rc);
rc.finish()?;
bitmap.save_to_file(path);
Ok(())
}
}
Now the problem is, when save_to_file
calls self.render(&mut rc)
, it cannot find the method because save_to_file
is implemented for C
and not CairoRenderContext
(although CairoRenderContext
implements RenderContext
).
|
14 | pub trait Renderable<C: piet_common::RenderContext> {
| - this type parameter
...
25 | self.render(&mut rc);
| ^^^^^^^ expected type parameter `C`, found struct `CairoRenderContext`
|
= note: expected mutable reference `&mut C`
found mutable reference `&mut CairoRenderContext<'_>`
As an awkward workaround, I added trait Drawable
that is implemented for any type of Renderable<CairoRenderContext>
. The problem is CairoRenderContext
has a lifetime parameter, struct CairoRenderContext<'a>
.
pub trait Renderable<C: piet_common::RenderContext> {
fn render(&self, ctx: &mut C);
}
pub trait Drawable {
fn save_to_file<'a>(&self, path: &Path) -> Result<(), piet_common::Error>
where
Self: Renderable<CairoRenderContext<'a>>,
{
let width = 512;
let height = 512;
let pix_scale = 1.0;
let mut device = piet_common::Device::new()?;
let mut bitmap: piet_common::BitmapTarget =
device.bitmap_target(width, height, pix_scale)?;
let mut rc = bitmap.render_context();
Renderable::<CairoRenderContext>::render(self, &mut rc);
// self.render(&mut rc);
rc.finish()?;
drop(rc);
bitmap.save_to_file(path);
// at this point, we don't need device, bitmap or rc.
// rustc thinks they should be alive.
Ok(())
}
}
The lifetime 'a
is supposed to represent the lifetime of a local variable device
but rustc errors that device
and bitmap
variables should live long enough for 'a
.
1 error[E0597]: `device` does not live long enough
|
| fn save_to_file<'a>(&self, path: &Path) -> Result<(), piet_common::Error>
| -- lifetime `'a` defined here
...
| device.bitmap_target(width, height, pix_scale)?;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ borrowed value does not live long enough
| let mut rc = bitmap.render_context();
| ----------------------- assignment requires that `device` is borrowed for `'a`
...
| }
| - `device` dropped here while still borrowed
2 error[E0597]: `bitmap` does not live long enough
|
| fn save_to_file<'a>(&self, path: &Path) -> Result<(), piet_common::Error>
| -- lifetime `'a` defined here
...
| let mut rc = bitmap.render_context();
| ^^^^^^^^^^^^^^^^^^^^^^^
| |
| borrowed value does not live long enough
| assignment requires that `bitmap` is borrowed for `'a`
...
| }
| - `bitmap` dropped here while still borrowed
3 error[E0505]: cannot move out of `bitmap` because it is borrowed
|
| fn save_to_file<'a>(&self, path: &Path) -> Result<(), piet_common::Error>
| -- lifetime `'a` defined here
...
| let mut rc = bitmap.render_context();
| -----------------------
| |
| borrow of `bitmap` occurs here
| assignment requires that `bitmap` is borrowed for `'a`
...
| bitmap.save_to_file(path);
| ^^^^^^ move out of `bitmap` occurs here
- Q1. When rustc errors about object-safety, was it a correct solution to uplift a type parameter from
render<C>
toRenderable<C>
? - Q2. Should I have two traits (Renderable, Drawable) so
save_to_file
can callrender
or is there a better way? - Q3. How can I correctly tell rustc about
Self: Renderable<CairoRenderContext<_>>
in the last example?
Solution 1:[1]
Your second error, regarding the &mut C
and &mut CairoRenderContext<'_>
mismatch, is solved by omitting the body of save_to_file()
from the trait definition, and instead defining it in the impl block. Here's a highly simplified version of what I mean:
pub trait Renderable<C: RenderContext> {
fn render(&self, ctx: &mut C);
fn save_to_file(&self);
}
struct Cairo;
impl Renderable<CairoRenderContext<'_>> for Cairo {
fn render(&self, ctx: &mut CairoRenderContext<'_>) {
todo!()
}
fn save_to_file(&self) {
let mut rc = CairoRenderContext::new();
self.render(&mut rc);
}
}
The original approach seems conceptually wrong in that the Renderable
trait is supposed to be backend-agnostic, yet the default impl of save_to_file()
was using types specific to Cairo, thus causing the error.
Q1
Moving the generic from the method to the trait is indeed one strategy to make a trait object-safe. Other strategies are
- Making the method use dynamic dispatch for its arguments
- Marking the method with
where Self: Sized
(though you won't be able to call that method using dynamic dispatch)
Q2
You don't need two traits if you used the approach I described.
Q3
The answer is a higher-ranked trait bound:
pub trait Drawable {
fn save_to_file(&self, path: &Path) -> ...
where
Self: for<'a> Renderable<CairoRenderContext<'a>>,
{...}
}
Note you can make the bound a supertrait instead:
pub trait Drawable: for<'a> Renderable<CairoRenderContext<'a>> {
fn save_to_file(&self, path: &Path) -> ... {...}
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Brian Bowman |