Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to unbox elements contained in polymorphic vectors?

After reading this answer to "Vector of objects belonging to a trait", it looks like Rust does automatic unboxing. Is this the case?

My code doesn't compile and I don't understand how that answer's code could compile.

What is the correct way to unbox the elements of a polymorphic vector, one containing boxed traits?

I've read Rust by Example and the Box documentation and I can't see any method that looks like unbox().

My code is:

trait HasArea {
    fn area(&self) -> f64;
}

struct Circle {
    x: f64,
    y: f64,
    radius: f64,
}

impl HasArea for Circle {
    fn area(&self) -> f64 {
        std::f64::consts::PI * (self.radius * self.radius)
    }
}

struct Square {
    x: f64,
    y: f64,
    side: f64,
}

impl HasArea for Square {
    fn area(&self) -> f64 {
        self.side * self.side
    }
}

fn print_area<T: HasArea>(shape: T) {
    println!("This shape has an area of {}", shape.area());
}

fn main() {
    let c = Circle {
        x: 0.0f64,
        y: 0.0f64,
        radius: 1.0f64,
    };

    let s = Square {
        x: 0.0f64,
        y: 0.0f64,
        side: 1.0f64,
    };

    print_area(c);
    print_area(s);

    let vec: Vec<Box<HasArea>> = Vec::new();
    vec.push(Box::new(c));
    vec.push(Box::new(s));

    for x in vec {
        print_area(x)
    }
}

My error is:

   Compiling rustgraph v0.1.0 (file:///home/chris/lunch/rustgraph)
error[E0277]: the trait bound `Box<HasArea>: HasArea` is not satisfied
  --> src/main.rs:54:9
   |
54 |         print_area(x)
   |         ^^^^^^^^^^ the trait `HasArea` is not implemented for `Box<HasArea>`
   |
   = note: required by `print_area`
like image 648
fadedbee Avatar asked Nov 30 '22 22:11

fadedbee


2 Answers

You can dereference it like print_area(*x), but it won't work for other reasons: the Sized bound for the print_area argument. Your function needs to know the size of its arguments.

You have other problems in your code: you are trying to push into an immutable vector and you are trying to box moved values. These were moved after you used it in print_area().

My opinion is that it would be easier to make print_area a method which takes an immutable reference. This will work as you expected.

trait HasArea {
    fn area(&self) -> f64;
    fn print_area(&self) {
        println!("This shape has area of {}", self.area());
    }
}

struct Circle {
    x: f64,
    y: f64,
    radius: f64,
}

impl HasArea for Circle {
    fn area(&self) -> f64 {
        std::f64::consts::PI * (self.radius * self.radius)
    }
}

struct Square {
    x: f64,
    y: f64,
    side: f64,
}

impl HasArea for Square {
    fn area(&self) -> f64 {
        self.side * self.side
    }
}

fn print_area<T: HasArea>(shape: &T) {
    println!("This shape has an area of {}", shape.area());
}

fn main() {
    let c = Circle {
        x: 0.0f64,
        y: 0.0f64,
        radius: 1.0f64,
    };

    let s = Square {
        x: 0.0f64,
        y: 0.0f64,
        side: 1.0f64,
    };

    c.print_area();
    s.print_area();

    let mut vec: Vec<Box<HasArea>> = Vec::new();
    vec.push(Box::new(c));
    vec.push(Box::new(s));

    for x in vec {
        x.print_area();
    }
}
like image 93
coredump Avatar answered Dec 07 '22 22:12

coredump


To answer your direct question:

How to unbox elements contained in polymorphic vectors?

You cannot. Once something has been boxed and had the concrete type erased, that's it. A Box<SomeTrait> cannot be made back into a SomeConcreteType, because nothing knows what that concrete type is.


To solve the problem in the code... check the error message again:

the trait bound Box<HasArea>: HasArea is not satisfied

That's because a reference to a trait (or a box of a trait) does not implement that trait!

To allow your program to compile and run as you originally wrote it, you only need to implement the trait for boxes, and we might as well do references too:

impl<T: ?Sized> HasArea for Box<T>
    where T: HasArea
{
    fn area(&self) -> f64 { (**self).area() }    
}

impl<'a, T: ?Sized> HasArea for &'a T
    where T: HasArea
{
    fn area(&self) -> f64 { (**self).area() }    
}

This allows your fixed-up main to run:

fn main() {
    let c = Circle {
        x: 0.0f64,
        y: 0.0f64,
        radius: 1.0f64,
    };

    let s = Square {
        x: 0.0f64,
        y: 0.0f64,
        side: 1.0f64,
    };

    print_area(&c);
    print_area(&s);

    let vec: Vec<Box<HasArea>> = vec![Box::new(c), Box::new(s)];

    for x in vec {
        print_area(x)
    }
}

Here, we pass a reference of c and s to print_area, to avoid transferring ownership. We also use the vec! macro to construct the vector with much less ceremony.

like image 36
Shepmaster Avatar answered Dec 08 '22 00:12

Shepmaster