Reputation: 2758
Here's my code:
trait UnaryOperator {
fn apply(&self, expr: Expression) -> Expression;
}
pub enum Expression {
UnaryOp(UnaryOperator, Expression),
Value(i64)
}
Which gives the following errors:
error: the trait 'core::marker::sized' is not implemented for type 'parser::UnaryOperator'
note: 'parser::UnaryOperator' does not have a constant size known at compile-time
I'm not sure how to accomplish what I want. I've tried:
trait UnaryOperator: Sized {
...
}
As well as
pub enum Expression {
UnaryOp(UnaryOperator + Sized, Expression),
...
}
And neither solved the issue.
I've seen ways to possibly accomplish what I want with generics, but then it seems like two expressions with different operators would be different types, but that's not what I want. I want all expressions to be the same type regardless of what the operators are.
Upvotes: 1
Views: 572
Reputation: 431809
Traits do not have a known size - they are unsized. To see why, check this out:
trait AddOne {
fn add_one(&self) -> u8;
}
struct Alpha {
a: u8,
}
struct Beta {
a: [u8; 1024],
}
impl AddOne for Alpha {
fn add_one(&self) -> { 0 }
}
impl AddOne for Beta {
fn add_one(&self) -> { 0 }
}
Both Alpha
and Beta
implement AddOne
, so how big should some arbitrary AddOne
be? Oh, and remember that other crates may implement your trait sometime in the future.
That's why you get the first error. There are 3 main solutions (note that none of these solutions immediately fix your problem...):
Box<Trait>
. This is kind-of-similar-but-different to languages like Java, where you just accept an interface. This has a known size (a pointer's worth) and owns the trait. This has the downside of requiring an allocation. trait UnaryOperator {
fn apply(&self, expr: Expression) -> Expression;
}
pub enum Expression {
UnaryOp(Box<UnaryOperator>, Expression),
Value(i64)
}
trait UnaryOperator {
fn apply<'a>(&self, expr: Expression<'a>) -> Expression<'a>;
}
pub enum Expression<'a> {
UnaryOp(&'a UnaryOperator, Expression<'a>),
Value(i64)
}
Expression<A>
and Expression<B>
would have different types. Depending on your usage, this could be a problem. You wouldn't be able to easily create a Vec<Expression<A>>
if you had both. trait UnaryOperator {
fn apply<U>(&self, expr: Expression<U>) -> Expression<U>;
}
pub enum Expression<U>
where U: UnaryOperator
{
UnaryOp(U, Expression<U>),
Value(i64)
}
Now, all of these fail as written because you have a recursive type definition. Let's look at this simplification:
enum Expression {
A(Expression),
B(u8),
}
How big is Expression
? Well, it needs to have enough space to hold... an Expression
! Which needs to be able to hold an Expression
.... you see where this is going.
You need to add some amount of indirection here. Similar concepts to #1 and #2 apply - you can use a Box
or a reference to get a fixed size:
enum Expression {
A(Box<Expression>),
B(u8),
}
Upvotes: 5