When is it appropriate to implement Send + Sync for static dispatch - multithreading

I find myself playing around with warp.
I would like to pass a database Trait to a warp::Filter using static dispatch, such that the concrete database may vary.
I read that Send + Sync are unsafe to implement, but I don't fully understand when it is appropriate to implement them.
Is it sound to implement Send + Sync in this context:
use std::sync::{Arc, RwLock};
use warp::Filter;
use warp::reply::{json};
use serde_json::{Value};
pub trait IDatabase {
fn db_read(&self) -> Value;
fn db_write(&self, data: Value);
}
pub fn get_api_v0_test<T: IDatabase + Send + Sync>(
db: Arc<RwLock<T>>
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
warp::path!("api" / "v0" / "test")
.and(warp::get())
.map(move || {
json(&db.read().unwrap().db_read())
})
}
Edit:
Yes, this example is completely sound!
As #trentcl and #Masklinn pointed out, I am in fact not implementing Send + Sync, I am simply requiring that any type T passed into the filter, implements the Send + Sync Traits, which is completely safe Rust.

I read that Send + Sync are unsafe to implement, but I don't fully understand when it is appropriate to implement them.
Because Send and Sync are automatically derived, it is only appropriate to derive them explicitely when wrapping raw pointers, usually because you're wrapping a native library, and after having made sure that the trait is appropriate for the underlying type.
Is it sound to implement Send + Sync in this context:
I don't understand what you'd want to "implement Send + Sync" on in this context.
pub trait IDatabase {
Rust is not C#, IWhatever is not a thing.

Related

How to accept either ownerhip or mutable reference for the same method

I am making a library in Rust around a TCP protocol. And I am also trying to make it async runtime independent.
I would like to make it accept ownership of an existing connection OR accept a mutable reference to it. I.e. I want to let the caller say who should drop the connection.
I have this
pub struct ReadableStream<T: AsyncRead + AsyncWrite> {
reader: FramedRead<ReadHalf<T>, WordCodec>,
}
pub struct WriteableStream<T: AsyncRead + AsyncWrite> {
writer: FramedWrite<WriteHalf<T>, WordCodec>,
}
pub struct AsyncStream<T: AsyncRead + AsyncWrite> {
read_stream: ReadableStream<T>,
write_stream: WriteableStream<T>,
}
impl<T: AsyncRead + AsyncWrite> From<T> for AsyncStream<T> {
fn from(stream: T) -> Self {
let parts = stream.split();
Self {
read_stream: ReadableStream::new(parts.0),
write_stream: WriteableStream::new(parts.1),
}
}
}
impl<'stream, T: AsyncRead + AsyncWrite + Unpin> From<&'stream mut T>
for AsyncStream<&'stream mut T>
{
fn from(stream: &'stream mut T) -> Self {
let parts = stream.split();
Self {
read_stream: ReadableStream::new(parts.0),
write_stream: WriteableStream::new(parts.1),
}
}
}
But this fails to compile with "conflicting implementation for AsyncStream<&mut _>". If I uncomment the second implementation, the first one compiles. I can also compile only the second one... But I want to have both. I would expect either to be called based on whether from() was called like AsyncStream::from(source) or like AsyncStream::from(&mut source).
So how could I accomplish that?
Note: FramedRead/FramedWrite are from asynchronous-codec. AsyncRead, AsyncWrite, ReadHalf, WriteHalf are from the futures crate. WordCodec is my implementation of the protocol tailored to asynchronous-codec that is irrelevant to the question at hand.
I decided what I thought is biting the bullet by implementing a method in my struct ( AsyncStream ) to do the conversion, and maybe implement From for the specific runtimes I plan to test against, enabling users to use the struct's method when their runtime is not covered.
This approach works... but furthermore than that, it revealed that what I was trying to do is not exactly feasible for a different reason. I want to have the ability to split the connection into reader and writer, and I can't do that since split() takes ownership.
I'll solve the ownership/mutable reference problem by accepting ownership only, and providing a method that consumes self and returns the wrapped stream. Not exactly the ergonomics I was hoping for, but it addresses the scenario about wanting to let the user use my lib from a point, to a point, and switch from/to something else.

How to deal with trait types?

I come from JVM world and I'm struggling with dealing with the traits. My goal is to provide an interface (that what it would be called in Java) that would represent the time provider, so I could use 'real' implementation in production environment and 'fake' in the tests, so I could stub the time.
In Java I'd have
interface Clock {
Instant now();
}
class UtcClock implements Clock {
Instant now() {
return Instant.now();
}
}
and then I could use Clock type as any other type.
In Rust I have
pub trait Clock {
fn now(&self) -> DateTime<Utc>;
}
pub struct UtcClock;
impl Clock for UtcClock {
fn now(&self) -> DateTime<Utc> {
return Utc::now();
}
}
However to be able to use dynamic type Clock in Rust and to move it between threads I have to used boxed type e.g. Arc<dyn Clock + Send + Sync> which wouldn't be required if I'd use concrete type UtcClock.
Is provided solution of using traits idiomatic in Rust? Or there are other techniques to decouple the 'interface' and the implementation?
If it is OK then is there any way to make it look better than Arc<dyn Clock + Send + Sync>?
You don't have to use a trait object (i.e. a dyn Clock of some kind), you may also be able to use regular generics:
fn use_clock<T: Clock>(clock: T) {
let time = clock.now();
// ...
}
Though I imagine you'll have a struct containing various "services" and having lots of generics everywhere might get slightly messy.
As for trait objects, if you just want to "not have to think about it" and get Java-like behaviour, an Arc<dyn Clock> will largely do what you want, other than thread-safety related traits. To resolve those issues, you can write Arc<dyn Clock + Send + Sync + 'static>, but I prefer to make these supertraits:
trait Clock: Send + Sync + 'static /* + whatever other traits you want */ {
// ...
}
Note, this syntax doesn't do inheritance like the equivalent would in Java. Instead it imposes the restriction that: types which implement Clock must also implement Send, etc. Especially when working with unit structs (which are Send + Sync + 'static implicitly), moving the constraints to the trait definition can help cut down on boilerplate.
There's also type aliases, which essentially allow you to change the "spelling" of a type:
type ArcClock = Arc<dyn Clock + Send + Sync + 'static>;
and then you can use ArcClock like a regular type. Note, to the compiler, ArcClock is literally the same thing as Arc<dyn Clock + ...>.
As an aside, trait objects are often looked down upon in Rust for "bad performance". While it is true that calling a method on a trait object is generally harder to optimize than a regular monomorphized generic and requires an extra pointer indirection, this overhead is likely fairly small unless you're calling the method in a tight loop.
For example, in the case of a web server that stores the time a user was created, the overhead of the trait object will be negligible. FWIW, all java method calls behave in this way, so at worst you'll get Java-tier performance.
Arc<dyn Clock + Send + Sync> seems likely to be best, unless you're able to use &dyn Clock + Send + Sync. You can then use an alias type to make it look better, e.g. type DynClock = Arc<dyn Clock + Send + Sync>;. See the Rust book and the reference.

on-the-fly substitution of `Option<Arc<Mutex<Box<dyn T>>>>`

Suppose I have an object video_source: Option<Arc<Mutex<Box<dyn GetVideo>>>> and I pass it to a thread:
std::thread::spawn(||{
loop {
if let Some(video_source) = video_source {
let video_frame = video_source.lock().unwrap().get();
}
}
})
where
trait GetVideo {
fn get() -> Vec<u8>
}
What if I want to change the video source on the fly? Well, I'd do this on another thread:
video_frame.unwrap().lock().unwrap() = Box::new(other_source);
I want to make this idea more generic. I want a type that permits such thing. Here's my sketch:
use std::sync::{Arc, Mutex};
pub type OnTheFlyInner<T> = Box<T + Send + Sync>;
pub type OnTheFly<T> = Arc<Mutex<OnTheFlyInner<T>>>;
//I'd like this to be a method of `OnTheFly`
pub fn on_the_fly_substitute(on_the_fly: &mut Option<OnTheFly>, substitute_by: Option<OnTheFlyInner>) {
if let Some(substitute_by) = substitute_by {
if let Some(on_the_fly) = on_the_fly {
*on_the_fly.lock().unwrap() = substitute_by;
}
} else {
on_the_fly.take();
}
}
However, I cannot make something generic over T where T is a trait, it should be a type.
Any ideas?
Bounty
This is solved by #user4815162342. But what if I want to make one OnTheFly object point to the same thing as the other one?
First, you are correct that T cannnot be a trait like GetVideo; traits are not types. However, T can be dyn GetVideo.
Second, your aliases have generic parameters, so they should be reflected as such in the function signature:
pub fn on_the_fly_substitute<T>(on_the_fly: &mut Option<OnTheFly<T>>, substitute_by: Option<OnTheFlyInner<T>>)
^^^ ^^^ ^^^
Third, your alias looks like an attempt to constrain T to be Send + Sync, but aliases cannot define additional bounds. You would instead put them on the function (with ?Sized since you want to allow trait objects):
pub fn on_the_fly_substitute<T: ?Sized>(on_the_fly: &mut Option<OnTheFly<T>>, substitute_by: Option<OnTheFlyInner<T>>)
where
T: ?Sized + Send + Sync
{
...
}
Note: your function body does not require Send and Sync so these bounds should probably not be included.
Fourth, Option<Arc<Mutex<Box<dyn GetVideo>>>> is not thread safe. You'll need to constrain that the trait object is at least Send:
Option<Arc<Mutex<Box<dyn GetVideo + Send>>>>
^^^^^^
Fifth, a complete example is lacking, but you appear to be wanting multiple threads to modify the same video_source. This would likely not compile since you would need multiple threads to keep a &mut _ in order to change it.
If you want shared ownership of a value that might not exist, move the option into the Mutex and adjust your function and aliases accordingly:
video_source: Arc<Mutex<Option<Box<dyn GetVideo>>>>
Sixth, your comment "I'd like this to be a method of OnTheFly" is misguided. Aliases are just aliases, you'd need a method on the aliased Option/Arc type. Keep it as a free function, introduce an extension trait for it, or create it as a wrapper type instead of an alias if you want more fine-grained control.

Rust's `Sender/Receiver` is forced to be `Sync + Send`

pub struct AsyncTransporter {
stream: Arc<dyn AsyncRW + Send + Sync>,
}
Suppose that a library expects AsyncTransporter to be Send + Sync, and that dyn AsyncRW is a struct that holds a channel tuple:
pub struct SmolSocket {
channel: (Sender<Arc<Packet>>, Receiver<Arc<Packet>>),
}
How can I make it such that SmolSocket implements Send + Sync safely? Even though SmolSocket holds Sender and Receiver, it's going to be inside an Arc so I don't see how it could go wrong.
Unfortunately AsyncTransporter must be Send+Sync.
Context: AsyncTransporter is the connector in https://docs.rs/hyper/0.14.9/hyper/client/struct.Builder.html#method.build so it automatically needs to be Send+Sync. At least this is what I think.

Is there a more idiomatic way to use traits like `io::Read` with lifetimes?

I have a public trait, Parser, that defines an external interface. I then have a private ParserImpl struct that implements the methods (actually, I have several implementations, which is the idea behind using the trait to abstract away).
use std::io;
pub trait Parser {
// ...omitted
}
struct ParserImpl<R: io::Read> {
// ...omitted
stream: R,
}
impl<R: io::Read> ParserImpl<R> {
// ...methods
fn new(stream: R) -> ParserImpl<R> {
ParserImpl {
// ...omitted
stream: stream,
}
}
}
impl<R: io::Read> Parser for ParserImpl<R> {
// ...methods
}
To create a parser instance, I use a function to hide ParserImpl.
pub fn make_parser<'a, R>(stream: R) -> Box<Parser + 'a>
where
R: io::Read + 'a,
{
Box::new(ParserImpl::new(stream))
}
This is all well and good... and it works... but the make_parser function troubles me. I feel that there must be a simpler way to approach this and like I'm missing something important, as this seems like a potential pitfall whenever using a trait like io::Read to abstract away the source of data.
I understand the need to specify lifetimes (Parameter type may not live long enough?) but I am a bit stumped on whether I can have both a clean and simple interface, and also use a trait like io::Read.
Is there a "cleaner," or perhaps more idiomatic way, to use traits like io::Read that I am missing? If not, that's okay, but I'm pretty new to Rust and when I wrote the above function I kept thinking "this can't be right..."
To make this sample runnable, here's a main:
fn main() {
use std::fs;
let file: fs::File = fs::File::open("blabby.txt").unwrap();
let parser = make_parser(file);
}
That is the idiomatic way of writing the code that has that meaning, but you may not want that meaning.
For example, if you don't need to create a boxed trait object, you can just return the parameterized value directly, or in this case just use the result of ParserImpl::new. This is my default form until I know I need dynamic dispatch provided by some trait object.
You could also require the 'static lifetime instead of introducing a new lifetime 'a, but this reduces the range of allowed types that you can pass into make_parser:
pub fn make_parser<R>(stream: R) -> Box<Parser>
where
R: io::Read + 'static,
{
Box::new(ParserImpl::new(stream))
}

Resources