Let's have a struct containing a vector of cities and a new_city function adding City to the vector. However, I got BorrowMutError which makes sense.
What should I do so I can call new_city multiple times (see below)?
I would need to drop the borrow_mut reference in the new_city function but I don't know how.
//use std::rc::Rc;
use std::cell::RefCell;
use std::cell::Ref;
pub struct Simulation{
cities: RefCell<Vec<City> >,
}
impl Simulation{
pub fn new() -> Simulation
{
Simulation{
cities: RefCell::new(Vec::new()),
}
}
pub fn new_city(&self, name: &'static str) -> Ref<City> {
let city = City::new(name);
self.cities.borrow_mut().push(city);
Ref::map(self.cities.borrow(), |vec| vec.last().unwrap())
}
}
#[derive(Debug, Copy, Clone)]
pub struct City {
name: &'static str,
}
impl City{
pub fn new(name: &'static str) -> City {
City { name: name, }
}
}
fn main(){
let mut simulation = Simulation::new();
let prg = simulation.new_city("Prague");
let brn = simulation.new_city("Brno");
let pls = simulation.new_city("Pilsen");
println!("{:?}", prg);
}
EDIT: Next use
Then I need prg and brn cities to add road between them with API (another vector in Simulation)
pub fn new_road(&self, from: &City, to: &City, time: i32) -> &Road {
//Adding information about road to simulation
}
let d1 = simulation.new_road(&prg, &brn, 120);
Therefore I cannot drop prg or brn.
You could simply introduce a new scope at the end of which the BorrowMut gets dropped:
pub fn new_city(&self, name: &'static str) -> Ref<City> {
{
let city = City::new(name);
self.cities.borrow_mut().push(city);
}
Ref::map(self.cities.borrow(), |vec| vec.last().unwrap())
}
But you can't hold on to the Refs across new_city calls either.
fn main() {
let mut simulation = Simulation::new();
let prg = simulation.new_city("Prague");
drop(prg);
let brn = simulation.new_city("Brno");
}
You might want to wrap the cities in Rcs to be able to hold on to them across pushes:
pub struct Simulation{
cities: RefCell<Vec<Rc<City>>>,
}
//...
impl Simulation {
//...
pub fn new_city(&self, name: &'static str) -> Rc<City> {
{
let city = Rc::new(City::new(name));
self.cities.borrow_mut().push(city);
}
self.cities.borrow().last().unwrap().clone()
}
}
Related
I'm trying to implement a pattern where different Processors can dictate the input type they take and produce a unified output (currently a fixed type, but I'd like to get it generic once this current implementation is working).
Below is a minimal example:
use std::convert::From;
use processor::NoOpProcessor;
use self::{
input::{Input, InputStore},
output::UnifiedOutput,
processor::{MultiplierProcessor, Processor, StringProcessor},
};
mod input {
use std::collections::HashMap;
#[derive(Debug)]
pub struct Input<T>(pub T);
#[derive(Default)]
pub struct InputStore(HashMap<String, String>);
impl InputStore {
pub fn insert<K, V>(mut self, key: K, value: V) -> Self
where
K: ToString,
V: ToString,
{
let key = key.to_string();
let value = value.to_string();
self.0.insert(key, value);
self
}
pub fn get<K, V>(&self, key: K) -> Option<Input<V>>
where
K: ToString,
for<'a> &'a String: Into<V>,
{
let key = key.to_string();
self.0.get(&key).map(|value| Input(value.into()))
}
}
}
mod processor {
use super::{input::Input, output::UnifiedOutput};
use super::I32Input;
pub struct NoOpProcessor;
pub trait Processor {
type I;
fn process(&self, input: &Input<Self::I>) -> UnifiedOutput;
}
impl Processor for NoOpProcessor {
type I = I32Input;
fn process(&self, input: &Input<Self::I>) -> UnifiedOutput {
UnifiedOutput(input.0 .0)
}
}
pub struct MultiplierProcessor(pub i32);
impl Processor for MultiplierProcessor {
type I = I32Input;
fn process(&self, input: &Input<Self::I>) -> UnifiedOutput {
UnifiedOutput(input.0 .0 * self.0)
}
}
pub struct StringProcessor;
impl Processor for StringProcessor {
type I = String;
fn process(&self, input: &Input<Self::I>) -> UnifiedOutput {
UnifiedOutput(input.0.parse().unwrap())
}
}
}
mod output {
#[derive(Debug)]
pub struct UnifiedOutput(pub i32);
}
pub fn main() {
let input_store = InputStore::default()
.insert("input_a", 123)
.insert("input_b", 567)
.insert("input_c", "789");
let processors = {
let mut labelled_processors = Vec::new();
// let mut labelled_processors: Vec<LabelledProcessor<Input<>>> = Vec::new(); // What's the correct type?
labelled_processors.push(LabelledProcessor("input_a", Box::new(NoOpProcessor)));
labelled_processors.push(LabelledProcessor(
"input_b",
Box::new(MultiplierProcessor(3)),
));
// labelled_processors.push(LabelledProcessor("input_c", Box::new(StringProcessor)));
labelled_processors
};
for processor in processors {
let output = retrieve_input_and_process(&input_store, processor);
println!("{:?}", output);
}
}
#[derive(Debug)]
pub struct I32Input(pub i32);
impl From<&String> for I32Input {
fn from(s: &String) -> Self {
Self(s.parse().unwrap())
}
}
struct LabelledProcessor<I>(&'static str, Box<dyn Processor<I = I>>)
where
for<'a> &'a String: Into<I>;
fn retrieve_input_and_process<T>(
store: &InputStore,
processor: LabelledProcessor<T>,
) -> UnifiedOutput
where
for<'a> &'a String: Into<T>,
{
let input = store.get(processor.0).unwrap();
processor.1.process(&input)
}
When // labelled_processors.push(LabelledProcessor("input_c", Box::new(StringProcessor))); is uncommented, I get the below compilation error:
error[E0271]: type mismatch resolving `<attempt2::processor::StringProcessor as attempt2::processor::Processor>::I == attempt2::I32Input`
--> src/attempt2.rs:101:63
|
101 | labelled_processors.push(LabelledProcessor("input_c", Box::new(StringProcessor)));
| ^^^^^^^^^^^^^^^^^^^^^^^^^ type mismatch resolving `<attempt2::processor::StringProcessor as attempt2::processor::Processor>::I == attempt2::I32Input`
|
note: expected this to be `attempt2::I32Input`
--> src/attempt2.rs:75:18
|
75 | type I = String;
| ^^^^^^
= note: required for the cast from `attempt2::processor::StringProcessor` to the object type `dyn attempt2::processor::Processor<I = attempt2::I32Input>`
I think I've learnt enough to "get" what the issue is - the labelled_processors vec expects all its items to have the same type. My problem is I'm unsure how to rectify this. I've tried to leverage dynamic dispatch more (for example changing LabelledProcessor to struct LabelledProcessor(&'static str, Box<dyn Processor<dyn Input>>);). However these changes spiral to their own issues with the type system too.
Other answers I've found online generally don't address this level of complexity with respect to the nested generics/traits - stopping at 1 level with the answer being let vec_x: Vec<Box<dyn SomeTrait>> .... This makes me wonder if there's an obvious answer that can be reached that I've just missed or if there's a whole different pattern I should be employing instead to achieve this goal?
I'm aware of potentially utilizing enums as wel, but that would mean all usecases would need to be captured within this module and it may not be able to define inputs/outputs/processors in external modules.
A bit lost at this point.
--- EDIT ---
Some extra points:
This is just an example, so things like InputStore basically converting everything to String is just an implementation detail. It's mainly to symbolize the concept of "the type needs to comply with some trait to be accepted", I just chose String for simplicity.
One possible solution would be to make retrieve_input_and_process a method of LabelledProcessor, and then hide the type behind a trait:
use std::convert::From;
use processor::NoOpProcessor;
use self::{
input::InputStore,
output::UnifiedOutput,
processor::{MultiplierProcessor, Processor, StringProcessor},
};
mod input {
use std::collections::HashMap;
#[derive(Debug)]
pub struct Input<T>(pub T);
#[derive(Default)]
pub struct InputStore(HashMap<String, String>);
impl InputStore {
pub fn insert<K, V>(mut self, key: K, value: V) -> Self
where
K: ToString,
V: ToString,
{
let key = key.to_string();
let value = value.to_string();
self.0.insert(key, value);
self
}
pub fn get<K, V>(&self, key: K) -> Option<Input<V>>
where
K: ToString,
for<'a> &'a str: Into<V>,
{
let key = key.to_string();
self.0.get(&key).map(|value| Input(value.as_str().into()))
}
}
}
mod processor {
use super::{input::Input, output::UnifiedOutput};
use super::I32Input;
pub struct NoOpProcessor;
pub trait Processor {
type I;
fn process(&self, input: &Input<Self::I>) -> UnifiedOutput;
}
impl Processor for NoOpProcessor {
type I = I32Input;
fn process(&self, input: &Input<Self::I>) -> UnifiedOutput {
UnifiedOutput(input.0 .0)
}
}
pub struct MultiplierProcessor(pub i32);
impl Processor for MultiplierProcessor {
type I = I32Input;
fn process(&self, input: &Input<Self::I>) -> UnifiedOutput {
UnifiedOutput(input.0 .0 * self.0)
}
}
pub struct StringProcessor;
impl Processor for StringProcessor {
type I = String;
fn process(&self, input: &Input<Self::I>) -> UnifiedOutput {
UnifiedOutput(input.0.parse().unwrap())
}
}
}
mod output {
#[derive(Debug)]
pub struct UnifiedOutput(pub i32);
}
pub fn main() {
let input_store = InputStore::default()
.insert("input_a", 123)
.insert("input_b", 567)
.insert("input_c", "789");
let processors = {
let mut labelled_processors: Vec<Box<dyn LabelledProcessorRef>> = Vec::new();
labelled_processors.push(Box::new(LabelledProcessor(
"input_a",
Box::new(NoOpProcessor),
)));
labelled_processors.push(Box::new(LabelledProcessor(
"input_b",
Box::new(MultiplierProcessor(3)),
)));
labelled_processors.push(Box::new(LabelledProcessor(
"input_c",
Box::new(StringProcessor),
)));
labelled_processors
};
for processor in processors {
let output = processor.retrieve_input_and_process(&input_store);
println!("{:?}", output);
}
}
#[derive(Debug)]
pub struct I32Input(pub i32);
impl From<&str> for I32Input {
fn from(s: &str) -> Self {
Self(s.parse().unwrap())
}
}
struct LabelledProcessor<I>(&'static str, Box<dyn Processor<I = I>>);
impl<I> LabelledProcessorRef for LabelledProcessor<I>
where
for<'a> &'a str: Into<I>,
{
fn retrieve_input_and_process(&self, store: &InputStore) -> UnifiedOutput {
let input = store.get(self.0).unwrap();
self.1.process(&input)
}
}
trait LabelledProcessorRef {
fn retrieve_input_and_process(&self, store: &InputStore) -> UnifiedOutput;
}
UnifiedOutput(123)
UnifiedOutput(1701)
UnifiedOutput(789)
I am facing an error that I have failed to understand properly, maybe I have insufficient knowledge on closure and there proper use. The error is can't capture dynamic environment in a fn item use the `|| { ... }` closure form instead I have defined closures for the handle_app_add_new_app in the AppParams impl as well. All code has been shared below.
main.rs
use std::collections::HashMap;
use std::fs::OpenOptions;
use std::{str, io::Write as write};
use std::env;
mod utils;
use utils::*;
fn main() {
let args: Vec<String> = env::args().collect();
let chain_id = args[0];
let chain_address = args[1];
let appname = args[2];
let abi_endpoint = args[3];
let app_address = args[4];
let token_name = args[5];
let token_address = args[6];
let new_token = &TokenParams::new(&token_name, &token_address);
let mut chain_tokens = HashMap::new();
// Add new tokens to chain.
chain_tokens.insert(
new_token.token_name,
new_token.token_address,
);
let app = AppParams::new(
&appname, &abi_endpoint, &app_address, &chain_tokens
);
pub unsafe fn convert_to_u8<T: Sized>(p: &T) -> &[u8] {
::std::slice::from_raw_parts(
(p as *const T) as *const u8,
::std::mem::size_of::<T>(),
)
}
//add new application(dex,payments...etc)
pub async fn handle_add_new_app(chain_id: &str, chain_address: &str,) -> Box<dyn Fn(ChainParams) -> ChainParams>{
let new_app= Box::new(|x| ChainParams::new(
&chain_id,
&chain_address,
app,
));
let bytes: &[u8] = unsafe {convert_to_u8(&new_app) };
let mut updated_file = OpenOptions::new().append(true).open("config.json").expect("Unable to open file");
updated_file.write_all(bytes).expect("adding app to config failed");
new_app
}
}
The utils file
use std::{error::Error, collections::HashMap};
use serde_json::Value;
#[derive(serde::Deserialize)]
pub struct Addresses{
pub to_address: String,
pub from_address: String,
}
impl Addresses{}
#[derive(serde::Deserialize)]
pub struct TokenParams {
pub token_name: String,
pub token_address: String,
}
impl TokenParams{
/// create a new token instance
pub fn new(token_name: &str, token_address: &str) -> Self {
let token_name = token_name;
let token_address = token_address;
TokenParams {
token_address: token_address.to_string(),
token_name: token_name.to_string(),
}
}
pub fn get_token(chain: &str, app: &str) -> Result<Vec<Value>, Box<dyn Error>>{
let token_getter = {
// Load the first file into a string.
let config = std::fs::read_to_string("Config.json").unwrap();
// Parse the string into a dynamically-typed JSON structure.
serde_json::from_str::<Value>(&config).unwrap()
};
// Get the get tokens with chain and app(eg dex)
let chain_elements = token_getter[chain][app].as_array().unwrap();
Ok(chain_elements.to_vec())
}
}
#[derive(serde::Deserialize)]
pub struct AppParams {
pub appname: String,
pub abi_endpoint: String,
pub app_address: String,
#[serde(flatten)]
pub tokens: HashMap<String, String>
}
impl AppParams{
/// create a new App(dex, marketplace...etc) instance
pub fn new(appname: &str, abi_endpoint: &str, address: &str, tokens: &HashMap<String, String>) -> Box<dyn Fn(AppParams) -> AppParams> {
let appname = appname;
let abi_endpoint = abi_endpoint;
let address = address;
let mut app_tokens = HashMap::new();
Box::new(|x| AppParams {
appname: appname.to_string(),
abi_endpoint: abi_endpoint.to_string(),
tokens: app_tokens,
app_address: address.to_string(),
})
}
pub fn get_app_params(self, chain: &str) -> Result<Vec<Value>, Box<dyn Error>>{
let app_getter = {
// Load the first file into a string.
let config = std::fs::read_to_string("Config.json").unwrap();
// Parse the string into a dynamically-typed JSON structure.
serde_json::from_str::<Value>(&config).unwrap()
};
// Get the get tokens with chain and app(eg dex)
let chain_elements = app_getter[chain][self.appname].as_array().unwrap();
Ok(chain_elements.to_vec())
}
}
#[derive(serde::Deserialize)]
pub struct ChainParams {
pub chain_id: String,
pub chain_address: String,
#[serde(flatten)]
pub chain_application: Vec<AppParams>,
}
impl ChainParams{
/// get an App(dex, marketplace...etc) instance
pub fn new(chain_id: &str, chain_address: &str, chain_application: AppParams ) -> Self {
let chain_id = chain_id;
let chain_address = chain_address;
let new_tokens = HashMap::new();
let new_application = AppParams::new(
&chain_application.appname,
&chain_application.abi_endpoint,
&chain_application.app_address,
&new_tokens,
);
ChainParams {
chain_id: chain_id.to_string(),
chain_address: chain_address.to_string(),
chain_application: vec![new_application(chain_application)],
}
}
//get chain params
pub fn get_chain_params(self) -> Result<Vec<Value>, Box<dyn Error>>{
let app_getter = {
// Load the first file into a string.
let config = std::fs::read_to_string("Config.json").unwrap();
// Parse the string into a dynamically-typed JSON structure.
serde_json::from_str::<Value>(&config).unwrap()
};
// Get the get tokens with chain and app(eg dex)
let chain_elements = app_getter[self.chain_id].as_array().unwrap();
Ok(chain_elements.to_vec())
}
}
A detailed explanation of your answer will be highly appreciated.
Minimized example, produced by removing everything irrelevant to the error:
fn main() {
let app = ();
pub fn captures_app() {
let _ = app;
}
}
Playground
The problem here is the fact that the functions are just functions, they doesn't hold any state. Therefore, they can't refer to anything other then their arguments and static variables, even if they're placed inside another function; the placement governs only the name scoping, i.e. "where this identifier will refer to this function", not the access to the environment.
To see why this is a problem, consider this:
fn returns_fn() -> fn() {
let app = String::from("app");
pub fn captures_app() {
println!("{}", app);
}
captures_app
}
Here, fn() is a function pointer. Every function can be coerced to the function pointer, therefore it must be callable by using the pointer only - but it depends on the app string, which can very well be different for every returns_fn's call and therefore can't be integrated into the returned pointer.
Now, what to do about it? There are two possible courses of action.
You can pass everything the function needs via its arguments. That is, the first example would become:
fn main() {
let app = ();
pub fn captures_app(app: ()) {
let _ = app;
}
}
and your own code will look as something like this:
pub async fn handle_add_new_app(
chain_id: &str,
chain_address: &str,
app: <return type of AppParams::new>
) -> Box<dyn Fn(ChainParams) -> ChainParams> {
// implementation
}
Or you can make the function a closure instead. In minimized example, it'll be like this:
fn main() {
let app = ();
let captures_app = || {
let _ = app;
}
}
In your code, that's slightly more tricky, because async closures are unstable yet, but you can do it like this:
let handle_add_new_app = move |chain_id: &str, chain_address: &str| -> Box<dyn Fn(ChainParams) -> ChainParams> {
async move {
// implementation
}
}
What is proper way of doing that? What options are available? I am aware about the problem of dangled references and the problem of self-referential struct. Still I have strong intuition it's a reachable problem because both owner and borrowed reference are returned and no memory deallocation happen. Also it's quite general problem! In theory, it's a solvable one.
use byte_slice_cast::*;
fn main() {
let context = context_make();
dbg!(&context);
}
//
fn context_make<'a>() -> Context<'a> {
Context::<'a>::new()
}
//
#[derive(Debug)]
struct Context<'a> {
pub dst_buffer: Box<[f32]>,
pub dst_buffer_bytes: &'a [u8],
}
//
impl<'a> Context<'a> {
fn new() -> Context<'a> {
let len: usize = 13;
let dst_buffer: Box<[f32]> = vec![0_f32; len].into_boxed_slice();
let dst_buffer_bytes = dst_buffer.as_byte_slice();
Context {
dst_buffer,
dst_buffer_bytes,
}
}
}
Note: this code requires byte-slice-cast = "1.2.0"
Interesting to compare solutions if there are more than one alternatives.
Playground
You can not do this in safe rust. There are good reasons for this like not being able to trivially move the struct without changing where the reference points to.
Instead, you would implement a function to get that reference:
struct Context {
pub dst_buffer: Box<[f32]>,
}
impl Context {
fn new() -> Context {
let len: usize = 13;
Context {
dst_buffer: vec![0_f32; len].into_boxed_slice(),
}
}
fn dst_buffer_bytes(&self) -> &[u8] {
self.dst_buffer.as_byte_slice()
}
}
And if you really want to print out the bytes too:
use std::fmt;
impl fmt::Debug for Context {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Context")
.field("dst_buffer", &self.dst_buffer)
.field("dst_buffer_bytes", self.dst_buffer_bytes())
.finish()
}
}
Solution I have found. It possible with help of create owning-ref. Interesting is it possible to reach the same result with help of standard Pin?
use byte_slice_cast::*;
use owning_ref::*;
fn main() {
let context = Context::new();
dbg!(&context);
dbg!(context.dst.as_owner());
dbg!(&*context.dst);
}
//
#[derive(Debug)]
struct Context {
// pub dst_buffer : Box::< [ f32 ] >,
// pub dst_buffer_bytes : &'a [ u8 ],
pub dst: OwningRef<Box<[f32]>, [u8]>,
}
//
impl Context {
fn new() -> Context {
let len: usize = 2;
let dst_buffer: Box<[f32]> = vec![0_f32; len].into_boxed_slice();
// let dst_buffer_bytes = dst_buffer.as_byte_slice();
let dst = OwningRef::new(dst_buffer);
let dst = dst.map(|dst_buffer| dst_buffer.as_byte_slice());
Context { dst }
// Context { dst_buffer, dst_buffer_bytes }
}
}
Playground
I'm making a functional builder that configures an object such as:
struct Person
{
name: String,
position: String
}
The builder itself keeps a list of boxed closures to be applied to the object when it needs to be constructed:
struct FunctionalBuilder<TSubject>
where TSubject : Default
{
actions: Vec<Box<dyn Fn(&mut TSubject) -> ()>>
}
impl<TSubject> FunctionalBuilder<TSubject>
where TSubject : Default
{
fn build(self) -> TSubject
{
let mut subj = TSubject::default();
for action in self.actions
{
(*action)(&mut subj);
}
subj
}
}
The idea being that one can aggregate this builder and then customize it for an object such as Person. Now, let's say I want to have a builder method called() that takes a name and saves the assignment of the name in the closure. I implement it as follows:
impl PersonBuilder
{
pub fn called(mut self, name: &str) -> PersonBuilder
{
let value = name.to_string();
self.builder.actions.push(Box::new(move |x| {
x.name = value.clone();
}));
self
}
}
Is this the right way of doing things? Is there a better way that avoids the temporary variable and clone() call?
Complete working example:
#[derive(Debug, Default)]
struct Person {
name: String,
position: String,
}
struct FunctionalBuilder<TSubject>
where
TSubject: Default,
{
actions: Vec<Box<dyn Fn(&mut TSubject) -> ()>>,
}
impl<TSubject> FunctionalBuilder<TSubject>
where
TSubject: Default,
{
fn build(self) -> TSubject {
let mut subj = TSubject::default();
for action in self.actions {
(*action)(&mut subj);
}
subj
}
fn new() -> FunctionalBuilder<TSubject> {
Self {
actions: Vec::new(),
}
}
}
struct PersonBuilder {
builder: FunctionalBuilder<Person>,
}
impl PersonBuilder {
pub fn new() -> Self {
PersonBuilder {
builder: FunctionalBuilder::<Person>::new(),
}
}
pub fn called(mut self, name: &str) -> PersonBuilder {
let value = name.to_string();
self.builder.actions.push(Box::new(move |x| {
x.name = value;
}));
self
}
pub fn build(self) -> Person {
self.builder.build()
}
}
pub fn main() {
let builder = PersonBuilder::new();
let me = builder.called("Dmitri").build();
println!("{:?}", me);
}
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=27eb6283836a478d5c68aa025aa4698d
You already do it, value is owned by your closure, the problem is that you require the Fn trait. This mean that action (the function) need to be able to be called many times. This mean value need to be cloned to keep it valid inside the closure. The closure can't give away its ownership.
One way would be to use FnOnce, which would make it possible to remove the clone, but this means that the builder can only be used once. To do this, use actions: Vec<Box<dyn FnOnce(&mut TSubject) -> ()>>, and action(&mut subj);.
More:
"cannot move a value of type FnOnce" when moving a boxed function
Fn* closure traits implemented for Box<dyn Fn*>
I'm writing tests using mock functions, controlling the return value among the tests with a Mutex:
#[macro_use]
extern crate lazy_static;
#[cfg(test)]
pub use mock::*;
#[cfg(not(test))]
pub use real::*;
mod real {
pub fn say_hello(_name: String) -> String {
unimplemented!()
}
}
/// simulate multiple uses, replace `real` in test.
mod mock {
use std::sync::*;
lazy_static! {
pub static ref LOCK: Mutex<bool> = Mutex::new(true);
pub static ref HELLO_VALUE: Mutex<String> = Mutex::new(String::default());
}
pub fn say_hello(_name: String) -> String {
use std::ops::Deref;
HELLO_VALUE.lock().unwrap().deref().clone()
}
pub fn set_hello_return_value(rtn: String) -> MutexGuard<bool> {
let lock = LOCK.lock().unwrap();
let mut value = HELLO_VALUE.lock().unwrap();
*value = rtn;
lock
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test1() {
// repeated block begin--------------------------
let _lock = LOCK.lock().unwrap();
let mut value = HELLO_VALUE.lock().unwrap();
*value = "Hello Tom!".to_string(); // just this line is different from test2
drop(value);
// repeat block end--------------------------
assert_eq!("Hello Tom!", say_hello("".to_string()));
}
#[test]
fn test2() {
// repeated block begin--------------------------
let _lock = LOCK.lock().unwrap();
let mut value = HELLO_VALUE.lock().unwrap();
*value = "Hello Jack!".to_string(); // just this line is different from test1
drop(value);
// repeat block end--------------------------
assert_eq!("Hello Jack!", say_hello("".to_string()));
}
#[test]
fn test_simplified_but_not_work() {
let _lock = set_hello_return_value("Hello Mark!".to_string());
assert_eq!("Hello Mark!", say_hello("".to_string()));
}
}
You can see the repeat block that I want to simplify. I made a function set_hello_return_value but the compiler complained:
error[E0106]: missing lifetime specifier
--> src/main.rs:28:51
|
28 | pub fn set_hello_return_value(rtn: String) -> MutexGuard<bool> {
| ^^^^^^^^^^^^^^^^ expected lifetime parameter
|
= help: this function's return type contains a borrowed value with an elided lifetime, but the lifetime cannot be derived from the arguments
= help: consider giving it an explicit bounded or 'static lifetime
Please help me to correct it.
Read the complete error message:
consider giving it an explicit bounded or 'static lifetime
Doing so works:
pub fn set_hello_return_value(rtn: String) -> MutexGuard<'static, bool> {
let lock = LOCK.lock().unwrap();
let mut value = HELLO_VALUE.lock().unwrap();
*value = rtn;
lock
}
I'd probably not return the guard at all, however:
pub fn with_hello_return_value<S, F>(rtn: S, f: F)
where
S: Into<String>,
F: FnOnce(),
{
let _lock = LOCK.lock().unwrap();
*HELLO_VALUE.lock().unwrap() = rtn.into();
f()
}
#[test]
fn test_simplified() {
with_hello_return_value("Hello Mark!", || {
assert_eq!("Hello Mark!", say_hello("".to_string()));
});
}
Honestly, I wouldn't do any of this as conditional compilation is overkill. If you need to test components of your system separately, they shouldn't know about each other to start with; they should be dependency-injected. This has the additional benefit that each test can inject its own value, preserving the multithreaded nature of the tests.
fn thing_that_uses_say_hello<G>(greeter: &G, name: &str) -> String
where
G: Greeting,
{
greeter.say_hello(name.into())
}
trait Greeting {
fn say_hello(&self, name: &str) -> String;
}
struct RealGreeting;
impl Greeting for RealGreeting {
fn say_hello(&self, name: &str) -> String {
format!("Hello, {}", name)
}
}
#[cfg(test)]
mod test {
use super::*;
use std::cell::RefCell;
struct MockGreeting<'a> {
called_with: RefCell<Vec<String>>,
value: &'a str,
}
impl<'a> MockGreeting<'a> {
fn new(value: &'a str) -> Self {
Self {
value,
called_with: Default::default(),
}
}
}
impl<'a> Greeting for MockGreeting<'a> {
fn say_hello(&self, name: &str) -> String {
self.called_with.borrow_mut().push(name.to_owned());
self.value.to_owned()
}
}
#[test]
fn test1() {
let g = MockGreeting::new("Hello");
let r = thing_that_uses_say_hello(&g, "Tom");
assert_eq!("Hello", r);
assert_eq!(&*g.called_with.borrow(), &["Tom".to_string()]);
}
}