Conditional bounds on trait impl based on feature flag? - rust

Suppose I have code like:
impl<A> Stream for MyStruct<A>
where
A: MyTrait
{
// ...
}
But if a certain feature flag is enabled, then A also needs to be Send + Sync, so I need A: MyTrait + Send + Sync if that feature is enabled. Is there a way to do this without having to duplicate the implementation? It appears I can't use #[cfg(feature = "...")] in a where clause.

The cargo documentation states:
[..] features should be additive. That is, enabling a feature should not disable functionality.
By adding bounds to an implement block, you are essentially removing functionality, as that implement now works for fewer types.
You should instead find a design that adds functionality when a feature is enabled. For example, you could split your trait in two, where one of them has the additional bounds.

As #Emoun points out, this is not such a good idea, but it is possible:
#[cfg(feature = "asdf")]
trait MyTraitConditional: MyTrait {}
#[cfg(feature = "asdf")]
impl<T: MyTrait> MyTraitConditional for T {}
#[cfg(all(not(feature = "asdf"), feature = "bsdf"))]
trait MyTraitConditional: MyTrait + Send {}
#[cfg(all(not(feature = "asdf"), feature = "bsdf"))]
impl<T: MyTrait + Send> MyTraitConditional for T {}
Playground (with cfg_if for lowered verbosity)
Alternatively, you can shove the entire impl into a macro_rules that takes only the trait bound as a parameter, and then invoke the macro differently depending on features. (I've encountered similar in the wild, but I'm not a fan.)

Related

What is the idiomatic way in rust for a function accepts a closure as argument or return a closure?

What is the idiomatic way in rust for a function accepts a closure as argument or return a closure?
I see it can be done in at least the below 3 ways:
// 1
pub fn run_with_envs_guard1(envs: &HashMap<&str, &str>, f: &dyn FnOnce()) {}
// 2
pub fn run_with_envs_guard2(envs: &HashMap<&str, &str>, f: Box<dyn FnOnce()>) {}
// 3
pub fn run_with_envs_guard3<F: FnOnce()>(envs: &HashMap<&str, &str>, f: F) {}
Are there really some differences among these 3 ways? If yes, pls help to clarify, and which way is more idiomatic i should choose?
I am learning rust still, sorry if all the above ways are some bad/strange things.
Maybe a more specific question, why in way 1 and 2 i need the dyn keyword, but in 3 i don't, from my understanding, these all need dynamic dispatching, is it? as the actual function cannot be determined in compiling time
Abdul answers the first half of your question (and I agree completely with what he said), so I'll take a stab at the second half.
If you want to return a closure from a function, you can't return a type parameter, because that implies that you're returning an instance of any FnOnce, at the caller's choice. You can't return a &FnOnce, because you (usually) need to pass ownership to the caller. You could make it work with Box<FnOnce>, but that tends to just be clunky to work with. When returning closures from functions, I'm partial to the impl trait syntax.
pub fn test() -> impl FnOnce() {
|| { println!("It worked!") }
}
In argument position, writing impl FnOnce() as the type of something is equivalent to defining a type argument, as Abdul did in his answer. However, in return position, it's an entirely new feature that returns an opaque value. It says "I'm returning an FnOnce, and I'm not telling you which one it is". It's the same concept as a trait object, but without the overhead of throwing it in a box.
Responding to your edit
i don't, from my understanding, these all need dynamic dispatching, is it? as the actual function cannot be determined in compiling time
This is actually not necessarily true. If you see the dyn keyword, then there's definitely a dynamic (runtime) dispatch happening. To understand your other example, though, let's consider a simple trait that doesn't have the baggage of FnOnce.
pub trait MyTrait {}
struct Foo;
struct Bar;
impl MyTrait for Foo {}
impl MyTrait for Bar {}
pub fn example<T: MyTrait>(_arg: T) {
println!("It works!");
}
fn main() {
example(Foo);
example(Bar);
}
I claim there's no dynamic dispatch happening here. Rust monomorphizes functions with type parameters. That means that example is like a template function in C++. Every instantiation of it will end up being a separate function. So, really, during Rust's compilation, this will end up being more like
struct Foo;
struct Bar;
pub fn example1(_arg: Foo) {
println!("It works!");
}
pub fn example2(_arg: Foo) {
println!("It works!");
}
fn main() {
example1(Foo);
example2(Bar);
}
Two unrelated functions that happen to do something similar. Rust resolves all of the linkage statically, so there's no dispatch happening at runtime. In fact, we can prove it. Take the code I just posted above and compile it with debugging symbols on (rustc -g filename.rs). Then use a tool like nm (available on most Linux machines by default) to list all of the symbols in the linker table. Assuming you didn't turn any optimizations on, you should see two example functions. This is what they look like in my linker
0000000000005340 t _ZN10code7example17h46383f9ad372dc94E
00000000000053a0 t _ZN10code7example17h97b400359a146fcaE
or, with nm -C to demangle the function names
0000000000005340 t code::example
00000000000053a0 t code::example
Two different functions, each of which takes concrete arguments of specific types.
Your proposed FnOnce would work the same way.
pub fn run_with_envs_guard3<F: FnOnce()>(envs: &HashMap<&str, &str>, f: F) {}
Every closure in Rust has a distinct type, so every time this function is called, a new version of run_with_envs_guard3 will get made, specifically for that closure. That new function will know exactly what to do for the closure you just gave it. In 99% of cases, if you have optimizations turned on, these made-up local functions will get inlined and optimized out, so no harm done. But there's no dynamic dispatch here.
In the other two examples, we have a dyn FnOnce, which is more like what you'd expect coming from a traditionally object-oriented language. dyn FnOnce contains a dynamic pointer to some function somewhere that will be dispatched at runtime, the way you'd expect.
I would prefer the third one. Because the rust documentation suggest to Use FnOnce as a bound when you want to accept a parameter of function-like type and only need to call it once.
pub fn run_with_envs_guard3<F: FnOnce()>(envs: &HashMap<&str, &str>, f: F) {}
This means that the F to be bound by FnOnce(ie, F must implement FnOnce)

What is the difference between `dyn` and generics?

I'm reading some code and it has a consume function which makes it possible for me to pass my own function f.
fn consume<R, F>(self, _timestamp: Instant, len: usize, f: F) -> Result<R>
where
F: FnOnce(&mut [u8]) -> Result<R>,
I wrote some similar code, but like this:
pub fn phy_receive(
&mut self,
f: &mut dyn FnMut(&[u8])
) -> u8 {
and to be fair I don't know what is the difference, aside from FnOnce vs FnMut. What is the difference between using dyn vs a generic type parameter to specify this function?
Using dyn with types results in dynamic dispatch (hence the dyn keyword), whereas using a (constrained) generic parameter results in monomorphism.
General explanation
Dynamic dispatch
Dynamic dispatch means that a method call is resolved at runtime. It is generally more expensive in terms of runtime resources than monomorphism.
For example, say you have the following trait
trait MyTrait {
fn f(&self);
fn g(&self);
}
and a struct MyStruct which implements that trait. If you use a dyn reference to that trait (e.g. &dyn MyTrait), and pass a reference to a MyStruct object to it, what happens is the following:
A "vtable" data structure is created. This is a table containing pointers to the MyStruct implementations of f and g.
A pointer to this vtable is stored with the &dyn MyTrait reference, hence the reference will be twice its usual size; sometimes &dyn references are called "fat references" for this reason.
Calling f and g will then result in indirect function calls using the pointers stored in the vtable.
Monomorphism
Monomorphism means that the code is generated at compile-time. It's similar to copy and paste. Using MyTrait and MyStruct defined in the previous section, imagine you have a function like the following:
fn sample<T: MyTrait>(t: T) { ... }
And you pass a MyStruct to it:
sample(MyStruct);
What happens is the following:
During compile time, a copy of the sample function is created specifically for the MyStruct type. In very simple terms, this is as if you copied and pasted the sample function definition and replaced T with MyStruct:
fn sample__MyStruct(t: MyStruct) { ... }
The sample(MyStruct) call gets compiled into sample__MyStruct(MyStruct).
This means that in general, monomorphism can be more expensive in terms of binary code size (since you are essentially duplicating similar chunks of code, but for different types), but there's no runtime cost like there is with dynamic dispatch.
Monomorphism is also generally more expensive in terms of compile times: because it essentially does copy-paste of code, codebases that use monomorphism abundantly tend to compile a bit slower.
Your example
Since FnMut is just a trait, the above discussion applies directly to your question. Here's the trait definition:
pub trait FnMut<Args>: FnOnce<Args> {
pub extern "rust-call" fn call_mut(&mut self, args: Args) -> Self::Output;
}
Disregarding the extern "rust-call" weirdness, this is a trait just like MyTrait above. This trait is implemented by certain Rust functions, so any of those functions is analogous to MyStruct from above. Using &dyn FnMut<...> will result in dynamic dispatch, and using <T: FnMut<...>> will result in monomorphism.
My 2 cents and general advice
Certain situations will require you to use a dynamic dispatch. For example, if you have a Vec of external objects implementing a certain trait, you have no choice but to use dynamic dispatch. For example,
Vec<Box<dyn Debug>>. If those objects are internal to your code, though, you could use an enum type and monomorphism.
If your trait contains an associated type or a generic method, you will have to use monomorphism, because such traits are not object safe.
Everything else being equal, my advice is to pick one preference and stick with it in your codebase. From what I've seen, most people tend to prefer defaulting to generics and monomorphism.

Why impl RangeBounds<T> for Range<&T> requires T to be sized?

https://doc.rust-lang.org/src/core/ops/range.rs.html#979-986
impl<T> RangeBounds<T> for Range<&T> {
fn start_bound(&self) -> Bound<&T> {
Included(self.start)
}
fn end_bound(&self) -> Bound<&T> {
Excluded(self.end)
}
}
As you can see, T is not marked with ?Sized, which is preventing me from passing a Range<&[u8]> into an argument requires impl RangeBounds<[u8]>.
Are there some design considerations behind it? If this is intended, which is the proper way to pass a range of [u8]?
It's rather unfortunate, but adding the T: ?Sized bound breaks type inference in code like btreemap.range("from".."to") because the compiler can't choose between T = str and T = &str, both of which satisfy the bounds on range.
Some discussion of this has taken place on PR #64327. As far as I know there are no plans to relax the bounds on these impls in the future.
Instead of a Range<T>, you can use a tuple of Bound<T>s; for example, instead of takes_range("from".."to"), you can write the following instead:
use std::ops::Bound;
takes_range((Bound::Included("from"), Bound::Excluded("to"))
This will work because (Bound<&T>, Bound<&T>) does implement RangeBounds<T> even when T is !Sized.

Should I implement AddAssign on a newtype?

I've got a newtype:
struct NanoSecond(u64);
I want to implement addition for this. (I'm actually using derive_more, but here's an MCVE.)
impl Add for NanoSecond {
fn add(self, other: Self) -> Self {
self.0 + other.0
}
}
But should I implement AddAssign? Is it required for this to work?
let mut x: NanoSecond = 0.to();
let y: NanoSecond = 5.to();
x += y;
Will implementing it cause unexpected effects?
Implementing AddAssign is indeed required for the += operator to work.
The decision of whether to implement this trait will depend greatly on the actual type and kind of semantics that you are aiming for. This applies to any type of your own making, including newtypes. The most important prior is predictability: an implementation should behave as expected from the same mathematical operation. In this case, considering that the addition through Add is already well defined for that type, and nothing stops you from implementing the equivalent operation in-place, then adding an impl of AddAssign like so is the most predictable thing to do.
impl AddAssign for NanoSecond {
fn add_assign(&mut self, other: Self) {
self.0 += other.0
}
}
One may also choose to provide additional implementations for reference types as the second operand (e.g. Add<&'a Self> and AddAssign<&'a Self>).
Note that Clippy has lints which check whether the implementation of the arithmetic operation is sound (suspicious_arithmetic_impl and suspicious_op_assign_impl). As part of being predictable, the trait should behave pretty much like the respective mathematical operation, regardless of whether + or += was used. To the best of my knowledge though, there is currently no lint or API guideline suggesting to implement -Assign traits alongside the respective operation.

Providing Blanket Trait Implementations for a Custom Trait

I am implementing a quick geometry crate for practice, and I want to implement two structs, Vector and Normal (this is because standard vectors and normal vectors map through certain transformations differently). I've implemented the following trait:
trait Components {
fn new(x: f32, y: f32, z: f32) -> Self;
fn x(&self) -> f32;
fn y(&self) -> f32;
fn z(&self) -> f32;
}
I'd also like to be add two vectors together, as well as two normals, so I have blocks that look like this:
impl Add<Vector> for Vector {
type Output = Vector;
fn add(self, rhs: Vector) -> Vector {
Vector { vals: [
self.x() + rhs.x(),
self.y() + rhs.y(),
self.z() + rhs.z()] }
}
}
And almost the exact same impl for Normals. What I really want is to provide a default Add impl for every struct that implements Components, since typically, they all will add the same way (e.g. a third struct called Point will do the same thing). Is there a way of doing this besides writing out three identical implementations for Point, Vector, and Normal? Something that might look like this:
impl Add<Components> for Components {
type Output = Components;
fn add(self, rhs: Components) -> Components {
Components::new(
self.x() + rhs.x(),
self.y() + rhs.y(),
self.z() + rhs.z())
}
}
Where "Components" would automatically get replaced by the appropriate type. I suppose I could do it in a macro, but that seems a little hacky to me.
In Rust, it is possible to define generic impls, but there are some important restrictions that result from the coherence rules. You'd like an impl that goes like this:
impl<T: Components> Add<T> for T {
type Output = T;
fn add(self, rhs: T) -> T {
T::new(
self.x() + rhs.x(),
self.y() + rhs.y(),
self.z() + rhs.z())
}
}
Unfortunately, this does not compile:
error: type parameter T must be used as the type parameter for some local type (e.g. MyStruct<T>); only traits defined in the current crate can be implemented for a type parameter [E0210]
Why? Suppose your Components trait were public. Now, a type in another crate could implement the Components trait. That type might also try to implement the Add trait. Whose implementation of Add should win, your crate's or that other crate's? By Rust's current coherence rules, the other crate gets this privilege.
For now, the only option, besides repeating the impls, is to use a macro. Rust's standard library uses macros in many places to avoid repeating impls (especially for the primitive types), so you don't have to feel dirty! :P
At present, macros are the only way to do this. Coherence rules prevent multiple implementations that could overlap, so you can’t use a generic solution.

Resources