I'm implementing a simple task queue using redis in Rust, but am struggling to deserialize the returned values from redis into my custom types.
In total I thought of 3 approches:
Deserializing using serde-redis
Manually implementing the FromRedisValue trait
Serializing to String using serde-json > sending as string > then deserializing from string
The 3rd approach worked but feels artificial. I'd like to figure out either 1 or 2, both of which I'm failing at.
Approach 1 - serde-redis
I have a simple Task definition:
#[derive(Debug, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct Task {
pub id: u32,
pub desc: String,
}
which I'm trying to receive into a worker:
use serde_redis::RedisDeserialize;
pub fn execute_task(conn: &mut Connection) {
let task: Task = redis::cmd("LPOP")
.arg("task_q")
.query::<Task>(conn)
.unwrap()
.deserialize()
.unwrap();
println!("... executing task {} ...", task.id);
}
but I'm getting the following error:
error[E0277]: the trait bound `Task: FromRedisValue` is not satisfied
--> src/bin/worker.rs:38:10
|
38 | .query::<Task>(conn)
| ^^^^^ the trait `FromRedisValue` is not implemented for `Task`
error[E0599]: no method named `deserialize` found for struct `Task` in the current scope
--> src/bin/worker.rs:40:10
|
40 | .deserialize()
| ^^^^^^^^^^^ method not found in `Task`
So clearly I integrated the crate wrong, as it's not working. The documentation is super brief and the source code way over my head as a beginner - what could I be missing?
Approach 2 - manually implementing FromRedisValue
My naive approach:
impl FromRedisValue for Task {
fn from_redis_value(v: &Value) -> RedisResult<Self> {
let t = Task {
id: v.id,
desc: v.desc,
};
RedisResult::Ok(t)
}
fn from_redis_values(items: &[Value]) -> RedisResult<Vec<Self>> {
let tasks = items
.into_iter()
.map(|v| Task {
id: v.id,
desc: v.desc,
})
.collect();
RedisResult::Ok(tasks)
}
}
The errors I'm getting:
error[E0609]: no field `id` on type `&Value`
--> src/redis_tut.rs:203:19
|
203 | id: v.id,
| ^^
error[E0609]: no field `desc` on type `&Value`
--> src/redis_tut.rs:204:21
|
204 | desc: v.desc,
| ^^^^
// ...the same for the vector implementation
So clearly redis's Value doesn't have / know of the fields I want for Task. What's the right way to do this?
Redis doesn't define structured serialization formats. It mostly store strings and integers. So you have to choose or define your format for your struct.
A popular one is JSON, as you noticed, but if you just want to (de)serialize simple pairs of (id, description), it's not very readable nor convenient.
In such a case, you can define your own format, for example the id and the description with a dash in between:
#[derive(Debug, PartialEq)]
pub struct Task {
pub id: u32,
pub desc: String,
}
// assume a task is defined as "<id>-<desc>"
impl FromRedisValue for Task {
fn from_redis_value(v: &Value) -> RedisResult<Self> {
let v: String = from_redis_value(v)?;
if let Some((id, desc)) = v.split_once('-') {
if let Ok(id) = id.parse() {
Ok(Task {
id,
desc: desc.to_string(),
})
} else {
Err((ErrorKind::TypeError, "bad first token").into())
}
} else {
Err((ErrorKind::TypeError, "missing dash").into())
}
}
}
I actually wrote a crate for this kind of Problem, it provides Derive Macros that implements the redis::FromRedisValue and redis::ToRedisArgs traits from mitsuhiko / redis-rs for any struct in which every field's type also implements ToRedisArgs. Check it out redis-redive.
Related
I have the following struct:
#[derive(Default)]
pub struct AppState {
actors: HashMap<String, ActorValue>,
feature: HashMap<String, FeatureValue>,
}
Actors are registered when running the application upon receiving a network request (i.e., they are inserted into the HashMap). Furthermore, a user can create a new feature for which a certain actor may be required.
pub enum ActorValue {
Automotive(AutomotiveActor),
Power(PowerActor),
}
pub enum FeatureValue {
Automotive(AutomotiveFeature),
// ....
}
pub struct AutomotiveFeature {
pub actor_name: String,
// ... more Actor-related String fields
}
pub struct AutomotiveActor {
name: String,
// ... more String fields
}
So, when creating an instance of AutomotiveFeature I am currently cloning the name of the respective AutomotiveActor instance to populate the actor_name:
let automotive_actor = app_state.actors.iter()
.find(|x| matches!(x.1, ActorValue::Automotive(_)))
.map(|x| match x.1 {
ActorValue::Automotive(p) => Some(p),
_ => None,
})
.flatten();
match automotive_actor {
Some(a) => {
let feature = AutomotiveFeature { actor_name: a.name.clone() };
}
None => {}
}
However, I am essentially keeping redundant info. Ideally, I could just replace all the String fields relating to the actor in the feature with a reference:
pub struct AutomotiveFeature {
pub actor: &AutomotiveActor
}
But I am getting lifetime issues and I don't know how I can annotate them correctly, considering I have two HashMaps.
If I use:
pub struct AutomotiveFeature {
pub actor: &'static AutomotiveActor
}
I get the following errors:
error[E0502]: cannot borrow `*state` as mutable because it is also borrowed as immutable
--> crates/code/src/my_code.rs:146:13
|
38 | let automotive_actor: Option<&AutomotiveActor> = app_state
| __________________________________________________-
39 | | .actors()
| |_____________________________- immutable borrow occurs here
...
43 | ActorValue::Automotive(p) => Some(p),
| ------- returning this value requires that `*state` is borrowed for `'static`
...
146 | / app_state
147 | | .features_mut()
| |____________________________^ mutable borrow occurs here
error: lifetime may not live long enough
--> crates/code/src/my_code.rs:43:40
|
35 | app_state: &mut AppState,
| - let's call the lifetime of this reference `'1`
...
43 | ActorValue::Automotive(p) => Some(p),
| ^^^^^^^ returning this value requires that `'1` must outlive `'static`
I have already looked at similar post, such as "Store reference of struct in other struct". Unfortunately, I cannot use std::rc::Rc; because I get the error:
`Rc<AutomotiveActor>` cannot be sent between threads safely
I am getting lifetime issues and I don't know how I can annotate them correctly"
Note that you can only explain to the compiler how long something lives. You can't actually make an object live longer by annotating a lifetime. References do not own an object or keep it alive. Rc/Arc actually keep an object alive, so I have a suspicion that this is what you want.
The reason I want to have a reference is that I can then implement methods as part of AutomotiveActor and then directly call automotive_actor.start_car()
I suspect that start_car() modifies the automotive_actor and is therefore a mut fn. This completely renders your initial idea of using references impossible, because you can only ever have one mutable reference to an object.
Rc/Arc also only provide immutable access to the object, but you can combine them with RefCell/Mutex to create interior mutability.
Rc<AutomotiveActor> cannot be sent between threads safely
This makes me assume that your project is multi-threaded and therefore requires thread safety. This means you probably want to use Arc<Mutex>.
This is one possible layout:
use std::{
collections::HashMap,
sync::{Arc, Mutex},
};
#[derive(Default)]
pub struct AppState {
actors: HashMap<String, ActorValue>,
feature: HashMap<String, FeatureValue>,
}
pub enum ActorValue {
Automotive(Arc<Mutex<AutomotiveActor>>),
//Power(PowerActor),
}
pub enum FeatureValue {
Automotive(AutomotiveFeature),
// ....
}
pub struct AutomotiveFeature {
pub actor: Arc<Mutex<AutomotiveActor>>,
// ... more Actor-related String fields
}
pub struct AutomotiveActor {
name: String,
// ... more String fields
}
fn main() {
let mut app_state = AppState::default();
let new_actor = Arc::new(Mutex::new(AutomotiveActor {
name: String::from("MyActor"),
}));
app_state.actors.insert(
new_actor.lock().unwrap().name.clone(),
ActorValue::Automotive(Arc::clone(&new_actor)),
);
let automotive_actor = app_state
.actors
.iter()
.find(|x| matches!(x.1, ActorValue::Automotive(_)))
.map(|x| match x.1 {
ActorValue::Automotive(p) => Some(p),
_ => None,
})
.flatten();
match automotive_actor {
Some(a) => {
let feature = AutomotiveFeature {
actor: Arc::clone(a),
};
}
None => {}
}
}
I'm building a wrapper around a DLL. This DLL gives me access to a database engine which implements an OOP design pattern. This requires me to create multiple overlapping traits that cover all the functionality:
pub trait CursorStatement { /* ... */ }
pub trait CursorTable { /* ... */ }
pub trait CursorStatementTable { /* ... */ }
...
I want to be able to bring these traits in scope so that I can call the functions without having to list every trait. Right now I'm doing:
mod traittest;
use traittest::*;
fn test() -> Result<(), AceError> {
let t = traittest::Table::new(3, "ORDERS")?;
let c = traittest::Cursor { handle: 42 };
println!("t.fields={}", t.fields());
println!("c.fields={}", c.fields());
Ok(())
}
fn main() {
test().expect("success");
}
The problem with use foo::* is that it puts everything from the module into my namespace, which I don't want.
In the example above, I don't have to type traittest::Table or traittest::Cursor, I just have to type Table or Cursor. However, I want to have to prefix those objects with the module name so when I'm reading the code I know where the objects came from. I might want to create a Table object in my local file that is distinguished from the one coming from the module.
I also don't want to have to do the following because if I later have to add a new trait I will have to update a bunch of other source files that depend on this module:
mod traittest;
use traittest::{CursorStatement, CursorStatementTable, CursorTable, /* ... */};
I tried creating a Traits supertrait that would inherit all other traits as shown in Is there any way to create a type alias for multiple traits?, but it doesn't work because I can't implement the trait for anything because there's nothing that would be an implementation of every trait in the file:
pub trait Traits: CursorStatement, CursorTable, CursorStatementHandle, /* ... */ {}
If I could create a named scope for all the traits, that would work, but I can't figure out how to make Rust happy with this idea:
let traits = {
pub trait CursorTable { /* ... */ }
}
It looks like this trait_group macro might do the trick but it's not obvious to me how I could use it to solve my problem.
Here's my entire program
mod traittest {
#[derive(Debug)]
pub struct AceError {
code: u32,
description: String,
}
pub trait CursorTable {
fn get_handle(&self) -> u32; // impl's must write this function
fn fields(&self) -> String {
return format!("(get_handle() -> {})", self.get_handle());
}
}
pub struct Table {
pub handle: u32,
pub table_name: String,
}
pub struct Cursor {
pub handle: u32,
}
impl Table {
pub fn new(handle: u32, table_name: &str) -> Result<Table, AceError> {
let table = Table {
handle: handle,
table_name: table_name.to_string(),
};
return Ok(table);
}
}
impl CursorTable for Table {
fn get_handle(&self) -> u32 {
return self.handle;
}
}
impl CursorTable for Cursor {
fn get_handle(&self) -> u32 {
return self.handle;
}
}
pub trait Traits: CursorTable {} /* super trait to bring all other traits in scope */
}
use traittest::Traits;
fn test() -> Result<(), traittest::AceError> {
let t = traittest::Table::new(3, "ORDERS")?;
let c = traittest::Cursor { handle: 42 };
println!("t.fields={}", t.fields());
println!("c.fields={}", c.fields());
Ok(())
}
fn main() {
test().expect("success");
}
and here's the error I get:
warning: unused import: `traittest::Traits`
--> src/main.rs:49:5
|
49 | use traittest::Traits;
| ^^^^^^^^^^^^^^^^^
|
= note: `#[warn(unused_imports)]` on by default
error[E0599]: no method named `fields` found for struct `traittest::Table` in the current scope
--> src/main.rs:54:31
|
10 | fn fields(&self) -> String {
| ------
| |
| the method is available for `std::boxed::Box<traittest::Table>` here
| the method is available for `std::sync::Arc<traittest::Table>` here
| the method is available for `std::rc::Rc<traittest::Table>` here
...
15 | pub struct Table {
| ---------------- method `fields` not found for this
...
54 | println!("t.fields={}", t.fields());
| ^^^^^^ method not found in `traittest::Table`
|
= help: items from traits can only be used if the trait is in scope
help: the following trait is implemented but not in scope; perhaps add a `use` for it:
|
49 | use crate::traittest::CursorTable;
|
error[E0599]: no method named `fields` found for struct `traittest::Cursor` in the current scope
--> src/main.rs:55:31
|
10 | fn fields(&self) -> String {
| ------
| |
| the method is available for `std::boxed::Box<traittest::Cursor>` here
| the method is available for `std::sync::Arc<traittest::Cursor>` here
| the method is available for `std::rc::Rc<traittest::Cursor>` here
...
20 | pub struct Cursor {
| ----------------- method `fields` not found for this
...
55 | println!("c.fields={}", c.fields());
| ^^^^^^ method not found in `traittest::Cursor`
|
= help: items from traits can only be used if the trait is in scope
help: the following trait is implemented but not in scope; perhaps add a `use` for it:
|
49 | use crate::traittest::CursorTable;
|
I finally figured out a solution I can live with, in case anybody else is looking for a solution to this problem.
In your module where you define your traits, create a sub-module with all the traits, like so:
pub mod Traits
{
pub trait CursorTrait
{
fn get_handle ( &self ) -> u32; // impl's must write this function
fn fields ( &self ) -> String
{
return format! ( "(get_handle() -> {})", self.get_handle() );
}
}
}
Now in your other modules, if you want to bring the traits in scope without bringing in the entire module, you can just bring in the submodule, like so:
mod foo; use foo::Traits::*;
I am trying to use Encode::encode on structs with a derived implementation, but I am getting this error:
error[E0599]: no method named `encode` found for type `AnimalCollection<Animal>` in the current scope
--> src/main.rs:42:57
|
13 | struct AnimalCollection<A: AnimalTrait> {
| --------------------------------------- method `encode` not found for this
...
42 | println!("animal collection encoded = {:?}",animals.encode());
| ^^^^^^
|
= note: the method `encode` exists but the following trait bounds were not satisfied:
`AnimalCollection<Animal> : _IMPL_DECODE_FOR_Animal::_parity_codec::Encode`
= help: items from traits can only be used if the trait is implemented and in scope
= note: the following trait defines an item `encode`, perhaps you need to implement it:
candidate #1: `_IMPL_DECODE_FOR_Animal::_parity_codec::Encode`
This is the code:
use indexmap::map::IndexMap;
use parity_codec;
use parity_codec::{Decode, Encode};
#[derive(Clone, PartialEq, Eq, Encode, Decode, Debug)]
struct Animal {
name: String,
cell: u32,
}
trait AnimalTrait: Encode + Decode {}
impl AnimalTrait for Animal {}
#[derive(Clone, PartialEq, Eq, Encode, Decode, Debug)]
struct AnimalCollection<A: AnimalTrait> {
animals: IndexMap<String, A>,
}
impl<A: AnimalTrait> AnimalCollection<A> {
fn new() -> AnimalCollection<A> {
AnimalCollection {
animals: IndexMap::new(),
}
}
fn add(&mut self, name: String, a: A) {
self.animals.insert(name, a);
}
}
fn main() {
let a = Animal {
name: String::from("Tiger"),
cell: 10,
};
println!("animal struct encoded = {:?}", a.encode());
let mut animals = AnimalCollection::<Animal>::new();
animals.add(
String::from("Dog"),
Animal {
name: String::from("Dog"),
cell: 1,
},
);
animals.add(
String::from("Cat"),
Animal {
name: String::from("Cat"),
cell: 2,
},
);
println!("animal collection encoded = {:?}", animals.encode());
}
Why isn't it working despite that I have #[derive]d all the traits automatically? How do I fix it?
Since I am deriving the Encode and Decode traits, I shouldn't really implement anything on my own, or should I?
I tested this code and it works on the Animal struct but doesn't work on the AnimalCollection struct. I also tried to implement the Encode trait on AnimalCollection but immediately got "conflicting implementation" error, so I am kind of stuck on how to solve this.
Cargo.toml is a bit tricky, you need to use derive feature:
[package]
name = "encmap"
version = "0.0.1"
edition = "2018"
[dependencies]
parity-codec = { version = "3.3", features = ["derive"] }
indexmap = "1.0.2"
I am trying to create an in-memory database using HashMap. I have a struct Person:
struct Person {
id: i64,
name: String,
}
impl Person {
pub fn new(id: i64, name: &str) -> Person {
Person {
id: id,
name: name.to_string(),
}
}
pub fn set_name(&mut self, name: &str) {
self.name = name.to_string();
}
}
And I have struct Database:
use std::collections::HashMap;
use std::sync::Arc;
use std::sync::Mutex;
struct Database {
db: Arc<Mutex<HashMap<i64, Person>>>,
}
impl Database {
pub fn new() -> Database {
Database {
db: Arc::new(Mutex::new(HashMap::new())),
}
}
pub fn add_person(&mut self, id: i64, person: Person) {
self.db.lock().unwrap().insert(id, person);
}
pub fn get_person(&self, id: i64) -> Option<&mut Person> {
self.db.lock().unwrap().get_mut(&id)
}
}
And code to use this database:
let mut db = Database::new();
db.add_person(1, Person::new(1, "Bob"));
I want to change person's name:
let mut person = db.get_person(1).unwrap();
person.set_name("Bill");
The complete code in the Rust playground.
When compiling, I get a problem with Rust lifetimes:
error[E0597]: borrowed value does not live long enough
--> src/main.rs:39:9
|
39 | self.db.lock().unwrap().get_mut(&id)
| ^^^^^^^^^^^^^^^^^^^^^^^ temporary value does not live long enough
40 | }
| - temporary value only lives until here
|
note: borrowed value must be valid for the anonymous lifetime #1 defined on the method body at 38:5...
--> src/main.rs:38:5
|
38 | / pub fn get_person(&self, id: i64) -> Option<&mut Person> {
39 | | self.db.lock().unwrap().get_mut(&id)
40 | | }
| |_____^
How to implement this approach?
The compiler rejects your code because it violates the correctness model enforced by Rust and could cause crashes. For one, if get_person() were allowed to compile, one might call it from two threads and modify the underlying object without the protection of the mutex, causing data races on the String object inside. Worse, one could wreak havoc even in a single-threaded scenario by doing something like:
let mut ref1 = db.get_person(1).unwrap();
let mut ref2 = db.get_person(1).unwrap();
// ERROR - two mutable references to the same object!
let vec: Vec<Person> = vec![];
vec.push(*ref1); // move referenced object to the vector
println!(*ref2); // CRASH - object already moved
To correct the code, you need to adjust your design to satisfy the following constraints:
No reference can be allowed to outlive the referred-to object;
During the lifetime of a mutable reference, no other reference (mutable or immutable) to the object may exist..
The add_person method already complies with both rules because it eats the object you pass it, moving it to the database.
What if we modified get_person() to return an immutable reference?
pub fn get_person(&self, id: i64) -> Option<&Person> {
self.db.lock().unwrap().get(&id)
}
Even this seemingly innocent version still doesn't compile! That is because it violates the first rule. Rust cannot statically prove that the reference will not outlive the database itself, since the database is allocated on the heap and reference-counted, so it can be dropped at any time. But even if it were possible to somehow explicitly declare the lifetime of the reference to one that provably couldn't outlive the database, retaining the reference after unlocking the mutex would allow data races. There is simply no way to implement get_person() and still retain thread safety.
A thread-safe implementation of a read can opt to return a copy of the data. Person can implement the clone() method and get_person() can invoke it like this:
#[derive(Clone)]
struct Person {
id: i64,
name: String
}
// ...
pub fn get_person(&self, id: i64) -> Option<Person> {
self.db.lock().unwrap().get(&id).cloned()
}
This kind of change won't work for the other use case of get_person(), where the method is used for the express purpose of obtaining a mutable reference to change the person in the database. Obtaining a mutable reference to a shared resource violates the second rule and could lead to crashes as shown above. There are several ways to make it safe. One is by providing a proxy in the database for setting each Person field:
pub fn set_person_name(&self, id: i64, new_name: String) -> bool {
match self.db.lock().unwrap().get_mut(&id) {
Some(mut person) => {
person.name = new_name;
true
}
None => false
}
}
As the number of fields on Person grows, this would quickly get tedious. It could also get slow, as a separate mutex lock would have to be acquired for each access.
There is fortunately a better way to implement modification of the entry. Remember that using a mutable reference violates the rules unless Rust can prove that the reference won't "escape" the block where it is being used. This can be ensured by inverting the control - instead of a get_person() that returns the mutable reference, we can introduce a modify_person() that passes the mutable reference to a callable, which can do whatever it likes with it. For example:
pub fn modify_person<F>(&self, id: i64, f: F) where F: FnOnce(Option<&mut Person>) {
f(self.db.lock().unwrap().get_mut(&id))
}
The usage would look like this:
fn main() {
let mut db = Database::new();
db.add_person(1, Person::new(1, "Bob"));
assert!(db.get_person(1).unwrap().name == "Bob");
db.modify_person(1, |person| {
person.unwrap().set_name("Bill");
});
}
Finally, if you're worried about the performance of get_person() cloning Person for the sole reason of inspecting it, it is trivial to create an immutable version of modify_person that serves as a non-copying alternative to get_person():
pub fn read_person<F, R>(&self, id: i64, f: F) -> R
where F: FnOnce(Option<&Person>) -> R {
f(self.db.lock().unwrap().get(&id))
}
Besides taking a shared reference to Person, read_person is also allowing the closure to return a value if it chooses, typically something it picks up from the object it receives. Its usage would be similar to the usage of modify_person, with the added possibility of returning a value:
// if Person had an "age" field, we could obtain it like this:
let person_age = db.read_person(1, |person| person.unwrap().age);
// equivalent to the copying definition of db.get_person():
let person_copy = db.read_person(1, |person| person.cloned());
This post use the pattern cited as "inversion of control" in the well explained answer and just add only sugar for demonstrating another api for an in-memory db.
With a macro rule it is possible to expose a db client api like that:
fn main() {
let db = Database::new();
let person_id = 1234;
// probably not the best design choice to duplicate the person_id,
// for the purpose here is not important
db.add_person(person_id, Person::new(person_id, "Bob"));
db_update!(db #person_id => set_name("Gambadilegno"));
println!("your new name is {}", db.get_person(person_id).unwrap().name);
}
My opinionated macro has the format:
<database_instance> #<object_key> => <method_name>(<args>)
Below the macro implementation and the full demo code:
use std::collections::HashMap;
use std::sync::Arc;
use std::sync::Mutex;
macro_rules! db_update {
($db:ident # $id:expr => $meth:tt($($args:tt)*)) => {
$db.modify_person($id, |person| {
person.unwrap().$meth($($args)*);
});
};
}
#[derive(Clone)]
struct Person {
id: u64,
name: String,
}
impl Person {
pub fn new(id: u64, name: &str) -> Person {
Person {
id: id,
name: name.to_string(),
}
}
fn set_name(&mut self, value: &str) {
self.name = value.to_string();
}
}
struct Database {
db: Arc<Mutex<HashMap<u64, Person>>>, // access from different threads
}
impl Database {
pub fn new() -> Database {
Database {
db: Arc::new(Mutex::new(HashMap::new())),
}
}
pub fn add_person(&self, id: u64, person: Person) {
self.db.lock().unwrap().insert(id, person);
}
pub fn modify_person<F>(&self, id: u64, f: F)
where
F: FnOnce(Option<&mut Person>),
{
f(self.db.lock().unwrap().get_mut(&id));
}
pub fn get_person(&self, id: u64) -> Option<Person> {
self.db.lock().unwrap().get(&id).cloned()
}
}
In Java-speak, I am trying to create a collection (vector) of objects (strict instances), each one of which implements an interface (trait), so I can then iterate over the collection and call a method on all of them.
I have reduced it down to one sample file below which contains all the parts that I hope will make it easier to get answers.
// main.rs - try and compile using just "rustc main.rs"
use std::io::Result;
/// ////// Part 1
// Types used by implementors of the trait, and in creating a vector of implementors of the trai
pub struct SampleResult {
metric: String,
}
pub trait SampleRunner {
fn run(&self, &'static str) -> Result<SampleResult>;
}
pub struct Sample {
name: &'static str,
runner: &'static SampleRunner,
}
/// /////// Part 2
/// Create one specific static instance of such as Sample
static SAMPLE1: Sample = Sample {
name: "sample",
runner: &Sample1,
};
// need a struct to hold the run method to satisfy the trait???
struct Sample1;
// Implement the trait for this specific struct
impl SampleRunner for Sample1 {
fn run(&self, name: &'static str) -> Result<SampleResult> {
println!("Name: {}", name);
Ok(SampleResult { metric: "OK".to_string() })
}
}
/// /////// Part 3
/// Using the existing static instances of Sample struct, by creating a vector of references for them
/// then iterating over the vector and calling the trait method on each one
fn main() {
let sample_set: Vec<&Sample> = vec![&SAMPLE1];
for sample in sample_set.iter() {
match sample.runner.run(sample.name) {
Ok(result) => println!("Success"),
_ => panic!("failed"),
}
}
}
That particular example fails with the message:
error[E0277]: the trait bound `SampleRunner + 'static: std::marker::Sync` is not satisfied in `Sample`
--> <anon>:21:1
|
21 | static SAMPLE1: Sample = Sample {
| _^ starting here...
22 | | name: "sample",
23 | | runner: &Sample1,
24 | | };
| |__^ ...ending here: within `Sample`, the trait `std::marker::Sync` is not implemented for `SampleRunner + 'static`
|
= note: `SampleRunner + 'static` cannot be shared between threads safely
= note: required because it appears within the type `&'static SampleRunner + 'static`
= note: required because it appears within the type `Sample`
= note: shared static variables must have a type that implements `Sync`
But I have had many different problems depending on the approach I have taken, related to Sync, Sized, etc etc.
There are two little errors in the code. The first is described by the error, it tells us that the static value is not safe as it does not implement the Sync trait. It just tries to prepare for the case when the static value is manipulated from multiple threads. Here, the best solution is simply to mark the value as const. After that, there is some problem with the lifetime of &SAMPLE1 in main, can be solved by "using the let keyword to increase it's lifetime".
The code after these little modifications compiles, and looks like this:
use std::io::{Result};
pub struct SampleResult {
metric: String
}
pub trait SampleRunner {
fn run(&self, &'static str) -> Result<SampleResult>;
}
pub struct Sample {
name: &'static str,
runner: &'static SampleRunner
}
// Make it const
const SAMPLE1: Sample = Sample { name: "sample", runner: &Sample1 };
struct Sample1;
impl SampleRunner for Sample1 {
fn run(&self, name: &'static str) -> Result<SampleResult> {
println!("Name: {}", name);
Ok(SampleResult {metric: "OK".to_string() })
}
}
fn main() {
// Extend the lifetime of the borrow by assigning it to a scope variable
let borrowed_sample1 : &Sample = &SAMPLE1;
let sample_set: Vec<&Sample> = vec!(borrowed_sample1);
for sample in sample_set.iter() {
match sample.runner.run(sample.name) {
Ok(result) => println!("Success"),
_ => panic!("failed")
}
}
}
However, I see that you are not satisfied with the code as you have to create a new struct for every implementation of SampleRunner. There is another way, using lambda functions (the Rust docs just refers to them as Closures).
use std::io::{Result};
pub struct SampleResult {
metric: String
}
type SampleRunner = Fn(&'static str) -> Result<SampleResult>;
pub struct Sample {
name: &'static str,
runner: &'static SampleRunner
}
// Still const, use a lambda as runner
const SAMPLE1: Sample = Sample { name: "sample", runner: &|name| {
println!("Name: {}", name);
Ok(SampleResult {metric: "OK".to_string() })
} };
fn main() {
let borrowed_sample1 : &Sample = &SAMPLE1;
let sample_set: Vec<&Sample> = vec!(borrowed_sample1);
for sample in sample_set.iter() {
// Must parenthese sample.runner so rust knows its a field not a method
match (sample.runner)(sample.name) {
Ok(result) => println!("Success"),
_ => panic!("failed")
}
}
}