I'm about to give a trait as argument to store it through a contructor method called new
.
The trait for a struct type is given as argument here:
renderer.rs
use super::shapes::Shape;
pub struct Renderer;
impl Renderer{
pub fn set_shape<T : Shape>(&self, shape: T) -> T::Builder{
T::Builder::new(shape)
}
}
then the contructor of the Builder
specified by the associated type will be called
shapebuilder.rs
use super::shapes::Shape;
use super::shapes::Rectangle;
pub trait ShapeBuilder{
fn new<T:Shape>(shape: T) -> Self;
}
pub struct RectangleBuilder{
shape: Shape<Builder=RectangleBuilder>
}
impl ShapeBuilder for RectangleBuilder{
fn new<T:Shape>(shape: T) -> Self{
RectangleBuilder{
shape: shape as Rectangle
}
}
}
at this point I already want to point out the compiler output
compiler_output
error[E0277]: the trait bound `shapes::Shape<Builder=shapebuilder::RectangleBuilder> + 'static: std::marker::Sized` is not satisfied
--> shapes.rs:14:6
|
14 | impl Shape for Rectangle{
| ^^^^^
|
= note: `shapes::Shape<Builder=shapebuilder::RectangleBuilder> + 'static` does not have a constant size known at compile-time
= note: required because it appears within the type `shapebuilder::RectangleBuilder`
= note: required by `shapes::Shape`
error: aborting due to previous error
I found similar questions here on SO which told something about Boxing. I tried to Box every parameter type to solve the issue. Boxed it like this shape: Box<T>
. No success. Do I need to box at all? I understand the issue that the compiler is not able to resolve the size of a trait since the specific/concrete struct types can have different sizes depending on their field/properties. Still I'm not able to find a solution. Hope it's trivial.
shapes.rs
use super::shapebuilder::ShapeBuilder;
use super::shapebuilder::RectangleBuilder;
pub trait Shape{
type Builder: ShapeBuilder;
}
#[derive(Clone, Copy)]
pub struct Rectangle{
pub height: usize,
pub width: usize,
}
impl Shape for Rectangle{
type Builder = RectangleBuilder;
}
lib.rs
pub mod renderer;
mod shapes;
mod shapebuilder;
Well, the compiler is not really pointing at the source of the error. The problem is here:
pub struct RectangleBuilder {
shape: Shape<Builder=RectangleBuilder>
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this is an unsized type!
}
Shape
is a trait, and using it as a type yields an unsized type. We could box it to fix this error:
pub struct RectangleBuilder {
shape: Box<Shape<Builder=RectangleBuilder>>
}
But then, what do we do about the cast here?
impl ShapeBuilder for RectangleBuilder {
fn new<T: Shape>(shape: T) -> Self {
RectangleBuilder {
shape: shape as Rectangle
// ^^^^^^^^^^^^^^^^^^ can't cast a generic type!
}
}
}
If RectangleBuilder
will indeed be ready to accept any Shape
whose Builder
is RectangleBuilder
, then let's remove the cast and add the appropriate constraints where necessary.
pub mod renderer {
use super::shapes::Shape;
use super::shapebuilder::ShapeBuilder;
pub struct Renderer;
impl Renderer {
pub fn set_shape<T: Shape + 'static>(&self, shape: T) -> T::Builder {
T::Builder::new(shape)
}
}
}
mod shapes {
use super::shapebuilder::ShapeBuilder;
use super::shapebuilder::RectangleBuilder;
pub trait Shape {
type Builder: ShapeBuilder;
}
#[derive(Clone, Copy)]
pub struct Rectangle {
pub height: usize,
pub width: usize,
}
impl Shape for Rectangle {
type Builder = RectangleBuilder;
}
}
mod shapebuilder {
use super::shapes::Shape;
pub trait ShapeBuilder: Sized {
fn new<T: Shape<Builder=Self> + 'static>(shape: T) -> Self;
}
pub struct RectangleBuilder {
shape: Box<Shape<Builder=RectangleBuilder> + 'static>,
}
impl ShapeBuilder for RectangleBuilder {
fn new<T: Shape<Builder=Self> + 'static>(shape: T) -> Self {
RectangleBuilder {
shape: Box::new(shape)
}
}
}
}
The 'static
bound puts a limit on the references that can be stored in a particular Shape
instance. 'static
means that the implementations cannot contain references, unless they have the 'static
lifetime.
However, if you'll need to use Rectangle
's fields in RectangleBuilder
, then RectangleBuilder
should only accept Rectangle
s, rather than any shape. We can use associated types again to express this.
pub mod renderer {
use super::shapes::Shape;
use super::shapebuilder::ShapeBuilder;
pub struct Renderer;
impl Renderer {
pub fn set_shape<T: Shape>(&self, shape: T) -> T::Builder {
T::Builder::new(shape)
}
}
}
mod shapes {
use super::shapebuilder::ShapeBuilder;
use super::shapebuilder::RectangleBuilder;
pub trait Shape {
type Builder: ShapeBuilder<Shape=Self>;
}
#[derive(Clone, Copy)]
pub struct Rectangle {
pub height: usize,
pub width: usize,
}
impl Shape for Rectangle {
type Builder = RectangleBuilder;
}
}
mod shapebuilder {
use super::shapes::Shape;
use super::shapes::Rectangle;
pub trait ShapeBuilder: Sized {
type Shape: Shape + ?Sized;
fn new(shape: Self::Shape) -> Self;
}
pub struct RectangleBuilder {
shape: Rectangle,
}
impl ShapeBuilder for RectangleBuilder {
type Shape = Rectangle;
fn new(shape: Self::Shape) -> Self {
RectangleBuilder {
shape: shape
}
}
}
}
In ShapeBuilder
, we've added a Shape
associated type that specifies what type of Shape
each ShapeBuilder
will operate on. ShapeBuilder::new
now uses this associated type, instead of a type parameter, to specify the type of its operand. Note that the + ?Sized
bound is needed because otherwise there's an implicit + Sized
bound, and Rust complains that Shape
doesn't imply Sized
. Another way to fix this is to add : Sized
to Shape
's definition.
pub trait Shape: Sized {
type Builder: ShapeBuilder<Shape=Self>;
}
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