Create vector of objects implementing a trait in Rust - rust

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")
}
}
}

Related

Lifetime issues with rocket fairings, tokio-scheduler and crons

I'm currently building an API engine on top of Rocket, and as modern apps do, I wanted to include an automated scheduler to run async tasks (aka crons) while the Rocket API is running.
I decided to use Rocket fairings to enable the said scheduler built around tokio-schedule on the "liftoff" event.
The fact is, I set up all the required parts (logging into database, structs and traits), but I get a strange error while compiling regarding lifetimes of my fairing.
Here is a walk through of my code :
-> this is my "command" module, containing all structural parts to build and move commands (aka crons) with my application.
/// Synthetize a command execution result.
pub enum CommandResult {
SUCCESS,
ERROR(String),
SKIPPED(String),
}
/// Trait to define structs as runnable async crons with tokio_scheduler
#[async_trait]
pub trait Command: Send + Sync {
/// returns the current command name
fn get_command_name(&self) -> String;
/// returns the current command argument payload
fn get_command_args(&self) -> Option<HashMap<String, String>>;
/// returns the "cron_middleware"
fn get_cron_middleware(&self) -> CronLogMiddleware<CronLogRepository>;
/// real body for the command execution, must be overriden in impls.
async fn do_run(&self) -> Result<CommandResult>;
/// starts the command process by validating command lock, and registering an open cron log into database.
async fn begin(&self) -> Result<CronLog> {
// ...
}
/// ends the command process by releasing command lock, and registering the result of the command to an opened cron log into database.
async fn end(&self, cron_log: &CronLog, result: CommandResult) -> Result<()> {
// ...
}
/// hidden runner of commands, uses begin, end and do_run, and will be used by runner.
async fn run(&self) -> Result<()> {
// ...
}
/// generates a unique key for this command name + args, for locks purposes
fn generate_unicity_key(&self) -> String {
// ...
}
/// converts command args as a string payload
#[allow(clippy::or_fun_call)]
fn get_command_args_as_string(&self) -> String {
// ...
}
}
/// struct to move a command + its cron schedule into scheduler.
pub struct CommandHandle<T: Command + ?Sized + Send + Sync> {
pub command: Box<T>,
pub schedule: String,
}
Then, for testing purposes, I created a test command struct like this :
/// a testing purpose command
pub struct TestCommand {
pub name: String,
pub args: Option<HashMap<String, String>>,
pub cron_log_middleware: CronLogMiddleware<CronLogRepository>,
}
#[async_trait]
impl Command for TestCommand {
// accessors (get_... functions)
async fn do_run(&self) -> Result<CommandResult> {
debug!("executed !");
Ok(CommandResult::SUCCESS)
}
}
The rocket builder looks like this :
let mut sched = CronScheduler::default();
sched.add_cron(CommandHandle {
command: Box::new(TestCommand {
name: "app:test".to_string(),
args: None,
cron_log_middleware: cron_log_middleware.clone(),
}),
schedule: "*/1 * * * *".to_string(),
});
// then I add sched to rocket with .manage()
And the fairing looks like this :
/// a rocket fairing enabling async tasks (eg crons) while rocket is launching
#[derive(Default)]
pub struct CronScheduler {
crons: Vec<CommandHandle<dyn Command>>,
}
impl CronScheduler {
/// adds a cron (eg CommandHandle with a given command) to run with the scheduler.
pub fn add_cron(&mut self, cron: CommandHandle<dyn Command>) {
self.crons.push(cron);
}
}
#[rocket::async_trait]
impl Fairing for CronScheduler {
//...
v -- error is here
async fn on_liftoff(&self, _rocket: &Rocket<Orbit>) {
let sched = SchedulerBuilder::build().await;
for handle in self.crons.iter() {
let job = Job::new_cron_job_async(handle.schedule.as_str(), |_uid, _lock| {
Box::pin(async move {
handle.command.run().await;
})
})
.unwrap();
sched.add(job).await.unwrap();
}
sched.start().await.unwrap();
}
}
Aaaand I get this error :
error[E0759]: `self` has lifetime `'life0` but it needs to satisfy a `'static` lifetime requirement
--> src/core/fairings/cron_scheduler.rs:34:26
|
34 | async fn on_liftoff(&self, rocket: &Rocket<Orbit>) {
| ^^^^ this data with lifetime `'life0`...
...
39 | / Box::pin(async move {
40 | | handle.command.run().await;
41 | | })
| |__________________- ...is used and required to live as long as `'static` here
|
note: `'static` lifetime requirement introduced by the return type
--> src/core/fairings/cron_scheduler.rs:34:5
|
34 | async fn on_liftoff(&self, rocket: &Rocket<Orbit>) {
| ^^^^^ requirement introduced by this return type
...
39 | / Box::pin(async move {
40 | | handle.command.run().await;
41 | | })
| |__________________- because of this returned expression
And my cargo toml shortened:
[dependencies]
rocket = {version = "0.5.0-rc.2", features = ["json"]}
// ...
tokio-cron-scheduler = {version = "0.8.1", features = ["signal"]}
// ...
I tried different solutions, and remarked that this is the Command that is causing issues, as if I replace the content of the "Box::pin(...)" with something like a println!, nothing goes wrong.
I don't know if it's a conflict between async-trait and rocket async traits, or else, but I cannot figure it out.
Edit 1: shortened alot of code 'cause the ticket was way too long.
Edit 2: solution found thanks to the validated answer ; here is the final code patched if it can help anyone.
FTR, I did have to impl Clone myself (not using macros) and used the code of the pinned answer as a reference.
// command
pub struct CommandHandle<T: Command + ?Sized + Send + Sync> {
pub command: Arc<T>,
pub schedule: String,
}
impl<T> Clone for CommandHandle<T>
where
T: Command + ?Sized + Send + Sync,
{
fn clone(&self) -> Self {
Self {
command: self.command.clone(),
schedule: self.schedule.clone(),
}
}
}
// fairing
async fn on_liftoff(&self, _rocket: &Rocket<Orbit>) {
let sched = SchedulerBuilder::build().await;
for handle in self.crons.iter() {
let schedule = handle.schedule.clone();
let handle = handle.clone();
let job = Job::new_cron_job_async(schedule.as_str(), move |_uid, _lock| {
let handle = handle.clone();
Box::pin(async move {
handle.command.run().await.unwrap();
})
})
.unwrap();
sched.add(job).await.unwrap();
}
sched.start().await.unwrap();
}
Job::new_cron_job_async requires that your closure is 'static, but it is not since handle is a reference to self.crons.
Taking a quick look at your structure, if you use Arc instead of Box in CommandHandle, then it is easily cloned and thus can give a 'static handle to the cron job:
pub struct CommandHandle<T: Command + ?Sized + Send + Sync> {
pub command: Arc<T>, // <------------
pub schedule: String,
}
impl Clone for CommandHandle ...
for handle in self.crons.iter() {
let handle = handle.clone(); // <------------ vvvv
let job = Job::new_cron_job_async(handle.schedule.as_str(), move |_uid, _lock| {
let handle = handle.clone(); // <------------
Box::pin(async move {
handle.command.run().await;
})
})
.unwrap();
sched.add(job).await.unwrap();
}
Its hard to verify since your posted code is incomplete, but I believe you need both clones above since the function needs ownership to be 'static, but also must be FnMut in order to be called multiple times, so the handle can't be moved into the async block directly.

Rust enum and match : How does it distinguishes two different enums that takes in two different struct but same contents

I am trying to understand following enum from this repo
#[repr(C)]
#[derive(BorshSerialize, BorshDeserialize, Debug, Clone)]
pub struct InitEscrowArgs {
pub data: EscrowReceive,
}
#[repr(C)]
#[derive(BorshSerialize, BorshDeserialize, Debug, Clone)]
pub struct ExchangeArgs {
pub data: EscrowReceive,
}
#[derive(BorshSerialize, BorshDeserialize, Clone)]
pub enum EscrowInstruction {
InitEscrow(InitEscrowArgs),
Exchange(ExchangeArgs),
CancelEscrow(),
}
and it's use of it in this match from this repo.
pub fn process(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
let instruction = EscrowInstruction::try_from_slice(instruction_data)?;
match instruction {
EscrowInstruction::InitEscrow(args) => {
msg!("Instruction: Init Escrow");
Self::process_init_escrow(program_id, accounts, args.data.amount)
}
EscrowInstruction::Exchange(args) => {
msg!("Instruction: Exchange Escrow");
Self::process_exchange(program_id, accounts, args.data.amount)
}
EscrowInstruction::CancelEscrow() => {
msg!("Instruction: Cancel Escrow");
Self::process_cancel(program_id, accounts)
}
}
}
I understand that this try_from_slice method gets some sort of byte array and deserialize it.
I do not understand how it determines which enum value to use.
The enum has 3 choices, InitEscrow / Exchange / CancelEscrow, but what determines the match to know which one it is suppose to select?
Seem to me the InitEscrowArgs and ExchangeArgs both takes in same struct. Both containing data that is EscrowReceive data type.
Method try_from_slice is part of the BorshDeserialize trait, which is derived on the enum in question. So, the choice between enum variants is made by the implementation of deserializer.
To see what is really going on, I've built the simplest possible example:
use borsh::BorshDeserialize;
#[derive(BorshDeserialize)]
enum Enum {
Variant1(u8),
Variant2,
}
By using cargo expand and a little manual cleanup, we can get the following equivalent code:
impl borsh::de::BorshDeserialize for Enum {
fn deserialize(buf: &mut &[u8]) -> Result<Self, std::io::Error> {
let variant_idx: u8 = borsh::BorshDeserialize::deserialize(buf)?;
let return_value = match variant_idx {
0u8 => Enum::Variant1(borsh::BorshDeserialize::deserialize(buf)?),
1u8 => Enum::Variant2,
_ => {
let msg = format!("Unexpected variant index: {}", variant_idx);
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
msg,
));
}
};
Ok(return_value)
}
}
Where the inner deserialize calls refers to impl BorshDeserialize for u8:
fn deserialize(buf: &mut &[u8]) -> Result<Self> {
if buf.is_empty() {
return Err(Error::new(
ErrorKind::InvalidInput,
ERROR_UNEXPECTED_LENGTH_OF_INPUT,
));
}
let res = buf[0];
*buf = &buf[1..];
Ok(res)
}
So, it works the following way:
Deserializer tries to pull one byte from input; if there's none - this is an error.
This byte is interpreted as an index of enum variant; if it doesn't match to one of variants - this is an error.
If the variant contains any data, deserializer tries to pull this data from the input; if it fails (according to the inner type's implementation) - this is an error.

Is there a way to bring all the traits from a module in scope without also importing other types?

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::*;

In memory database design

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()
}
}

Lifetime Issue with Associated Types

I've been pulling my hair out over the last week due to this incredibly annoying issue with lifetimes.
The problem occurs when I try to put a reference to a Buffer inside a DataSource, which is then referenced to a DrawCommand. I'm getting the error: vertex_data_source does not live long enough.
src/main.rs:65:23: 65:41 error:
src/main.rs:65 data_source: &vertex_data_source
^~~~~~~~~~~~~~~~~~
src/main.rs:60:51: 67:2 note: reference must be valid for the block suffix following statement 3 at 60:50...
src/main.rs:60 let vertices = VertexAttributes::new(&buffer);
src/main.rs:61
src/main.rs:62 let vertex_data_source = factory.create_data_source(vertices);
src/main.rs:63
src/main.rs:64 let command: DrawCommand<ResourcesImpl> = DrawCommand {
src/main.rs:65 data_source: &vertex_data_source
...
src/main.rs:62:67: 67:2 note: ...but borrowed value is only valid for the block suffix following statement 4 at 62:66
src/main.rs:62 let vertex_data_source = factory.create_data_source(vertices);
src/main.rs:63
src/main.rs:64 let command: DrawCommand<ResourcesImpl> = DrawCommand {
src/main.rs:65 data_source: &vertex_data_source
src/main.rs:66 };
src/main.rs:67 }
It says vertex_data_source has to be valid for the block suffix following statement 3 at line 60. My interpretation of that error is that vertex_data_source should be defined before line 60. But to create the vertex_data_source in the first place I need access to those VertexAttributes on line 60, so I can't just swap the order around.
I feel like all the 'a lifetimes sprinkled over my code need to be split into 2 or maybe just removed, however I've tried every combination that seemed sensible and I'm not out of ideas.
Below is a greatly simplified example of my code that demonstrates the problem. I would really appreciate a sanity check and hopefully a fresh mind might be able to spot the issue. (every time before a few days of fiddling has produced a fix but this time I'm stumped).
use std::cell::RefCell;
use std::marker::PhantomData;
pub struct DrawCommand<'a, R: Resources<'a>> {
pub data_source: &'a R::DataSource
}
pub trait Resources<'a> {
type DataSource: 'a;
type Buffer: 'a;
}
pub struct DataSource<'a> {
id: u32,
attributes: Vec<VertexAttributes<'a, ResourcesImpl<'a>>>,
current_element_array_buffer_binding: RefCell<Option<Buffer<'a>>>
}
pub struct Buffer<'a> {
context: &'a GraphicsContextImpl
}
pub struct GraphicsContextImpl;
pub struct ResourcesImpl<'a> {
phantom: PhantomData<&'a u32> // 'a is the lifetime of the context reference
}
impl<'a> Resources<'a> for ResourcesImpl<'a> {
type Buffer = Buffer<'a>;
type DataSource = DataSource<'a>;
}
struct Factory<'a> {
context: &'a GraphicsContextImpl
}
impl<'a> Factory<'a> {
/// Creates a buffer
fn create_buffer<T>(&self) -> Buffer<'a> {
Buffer {
context: self.context
}
}
fn create_data_source(&self, attributes: Vec<VertexAttributes<'a, ResourcesImpl<'a>>>) -> DataSource<'a> {
DataSource {
id: 0,
attributes: attributes,
current_element_array_buffer_binding: RefCell::new(None)
}
}
}
fn main() {
let context = GraphicsContextImpl;
let factory = Factory {
context: &context
};
let buffer = factory.create_buffer::<u32>();
let vertices = VertexAttributes::new(&buffer);
let vertex_data_source = factory.create_data_source(vec!(vertices));
let command: DrawCommand<ResourcesImpl> = DrawCommand {
data_source: &vertex_data_source
};
}
pub struct VertexAttributes<'a, R: Resources<'a>> {
pub buffer: &'a R::Buffer,
}
impl<'a, R: Resources<'a>> VertexAttributes<'a, R> {
pub fn new(buffer: &'a R::Buffer) -> VertexAttributes<'a, R> {
VertexAttributes {
buffer: buffer
}
}
}
Thanks very much in advance.
EDIT:
I've updated the code to better reflect my actual implementation.
By the way - replacing this:
let vertex_data_source = factory.create_data_source(vec!(vertices));
With this:
let vertex_data_source = DataSource {
id: 0,
attributes: vec!(vertices),
current_element_array_buffer_binding: RefCell::new(None)
};
Doesn't solve the issue.
This allows your example to compile:
pub struct DrawCommand<'a : 'b, 'b, R: Resources<'a>> {
pub data_source: &'b R::DataSource
}
However, I'm finding it extremely difficult to create a more minimal example. As best I can determine, you have an issue because you are declaring that you will hold a reference to an item that itself has a reference, and that those two references need to have a common lifetime ('a). Through some combination of the other lifetimes, this is actually impossible.
Adding a second lifetime allows the reference to the DataSource to differ from the reference of the DataSource itself.
I'm still going to try to create a more minified example.

Resources