I am new to Rust and want to explore coding with an ECS while learning Rust.
Here is my code
use specs::{Component, DenseVecStorage, System, ReadStorage};
trait TradeableResource{}
#[derive(Component)]
struct Money(f64);
#[derive(Component)]
struct Food(f64);
impl TradeableResource for Food{}
#[derive(Component)] //compiler does not like this line
struct MarketMaker<T:TradeableResource>{
lot_size:T
}
#[derive(Component)]
struct FoodOffer {
lot_size:f64,
price:f64,
}
struct SetOffers;
impl<'a> System<'a> for SetOffers {
type SystemData = (
ReadStorage<'a, Food>,
ReadStorage<'a, MarketMaker<Food>>
);
fn run(&mut self, food: Self::SystemData) {
}
}
fn main() {
println!("Hello, world!");
}
The compiler is not happy that I want to make this struct a component. Full error output:
error[E0277]: `T` cannot be shared between threads safely
--> src\main.rs:13:10
|
13 | #[derive(Component)]
| ^^^^^^^^^ `T` cannot be shared between threads safely
|
= help: within `MarketMaker<T>`, the trait `std::marker::Sync` is not implemented for `T`
help: consider further restricting this bound with `+ std::marker::Sync`
--> src\main.rs:14:22
|
14 | struct MarketMaker<T:TradeableResource>{
| ^^^^^^^^^^^^^^^^^
= note: required because it appears within the type `MarketMaker<T>`
= note: required because of the requirements on the impl of `std::marker::Sync` for `std::ptr::Unique<MarketMaker<T>>`
= note: required because it appears within the type `alloc::raw_vec::RawVec<MarketMaker<T>>`
= note: required because it appears within the type `std::vec::Vec<MarketMaker<T>>`
= note: required because it appears within the type `specs::storage::storages::DenseVecStorage<MarketMaker<T>>`
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0277]: `T` cannot be sent between threads safely
--> src\main.rs:13:10
|
13 | #[derive(Component)]
| ^^^^^^^^^ `T` cannot be sent between threads safely
|
= help: within `MarketMaker<T>`, the trait `std::marker::Send` is not implemented for `T`
help: consider further restricting this bound with `+ std::marker::Send`
--> src\main.rs:14:22
|
14 | struct MarketMaker<T:TradeableResource>{
| ^^^^^^^^^^^^^^^^^
= note: required because it appears within the type `MarketMaker<T>`
= note: required because of the requirements on the impl of `std::marker::Send` for `std::ptr::Unique<MarketMaker<T>>`
= note: required because it appears within the type `alloc::raw_vec::RawVec<MarketMaker<T>>`
= note: required because it appears within the type `std::vec::Vec<MarketMaker<T>>`
= note: required because it appears within the type `specs::storage::storages::DenseVecStorage<MarketMaker<T>>`
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
According to the error output, there is a problem in the derive macro for Component right?
Does this mean that the library (specs) cannot support generic components? Or is there some (safe) way to make the compiler happy?
from what I can see it seems that the derive macro Component requires the structure it is applied to to be Send. As you are passing a generic parameter T, the compiler can only verify that this requirement is upheld by T if T would also be restricted to be Send.
A straightforward solution would be to add this constraint to your definition like so:
Seems to be a step in the right direction but now I have lifetime constraint issues.
error[E0392]: parameter `'a` is never used
--> src\main.rs:14:20
|
14 | struct MarketMaker<'a, T:TradeableResource + std::marker::Send + std::marker::Sync>{
| ^^ unused parameter
|
= help: consider removing `'a`, referring to it in a field, or using a marker such as `std::marker::PhantomData`
error[E0478]: lifetime bound not satisfied
--> src\main.rs:13:10
|
13 | #[derive(Component)]
| ^^^^^^^^^
|
note: lifetime parameter instantiated with the lifetime `'a` as defined on the impl at 14:20
--> src\main.rs:14:20
|
14 | struct MarketMaker<'a, T:TradeableResource + std::marker::Send + std::marker::Sync>{
| ^^
= note: but lifetime parameter must outlive the static lifetime
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0495]: cannot infer an appropriate lifetime for lifetime parameter `'a` due to conflicting requirements
--> src\main.rs:13:10
|
13 | #[derive(Component)]
| ^^^^^^^^^
|
note: first, the lifetime cannot outlive the lifetime `'a` as defined on the impl at 14:20...
--> src\main.rs:14:20
|
14 | struct MarketMaker<'a, T:TradeableResource + std::marker::Send + std::marker::Sync>{
| ^^
note: ...so that the types are compatible
--> src\main.rs:13:10
|
13 | #[derive(Component)]
| ^^^^^^^^^
= note: expected `specs::world::comp::Component`
found `specs::world::comp::Component`
= note: but, the lifetime must be valid for the static lifetime...
note: ...so that the type `specs::storage::storages::DenseVecStorage<MarketMaker<'_, T>>` will meet its required lifetime bounds
--> src\main.rs:13:10
|
13 | #[derive(Component)]
| ^^^^^^^^^
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
If you really like to stay with the lifetime: the compiler complains that you defined the lifetime as a generic parameter but you did not use it inside your structure. To fix this you could use PhantomData using the lifetime, but first I'd like to understand why you introduced the lifetime parameter at all
Because no references in Rust is zero sized, including reference to ZST like &(). &T has same size as the underlying machine architecture's pointer size if the T has statically known size, or can be larger if we don't know its size at compile time.
PhantomData<T> is a special type built in to the language itself. All those static analysis the compiler provides including type check and borrow check treat it as same as T. But in runtime, it doesn't occupies any space regardless of the T.
If I do not add the lifetime parameter, I get told "the parameter type T may not live long enough". Given the simplicity of my code, I think this is a requirement of the specs library. I have now added 'static lifetime, since all values of any types used for T will only contain owned values. Sound right?