'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_filecan callrenderor 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 | 
