I need to create operations for an operation sequence. The operations share the following behaviour. They can be evaluated, and at construction they can either be parametrized by a single i32 (eg. Sum) or not parametrized at all (eg. Id).
I create a trait Operation. The evaluate part is trivial.
trait Operation {
fn evaluate(&self, operand: i32) -> i32;
}
But I don't know how to describe the second demand. The first option is to simply let concrete implementations of Operation handle that behaviour.
pub struct Id {}
impl Id {
pub fn new() -> Id {
Id {}
}
}
impl Operation for Id {
fn evaluate(&self, operand: i32) -> i32 {
operand
}
}
pub struct Sum {
augend: i32,
}
impl Sum {
pub fn new(augend: i32) -> Sum {
Sum { augend }
}
}
impl Operation for Sum {
fn evaluate(&self, addend: i32) -> i32 {
augend + addend
}
}
Second option is a new function that takes an optional i32. Then the concrete implementations deal with the possibly redundant input. I find this worse than the first option.
trait Operation {
fn evaluate(&self, operand: i32) -> i32;
fn new(parameter: std::Option<i32>)
}
Google has lead me to mutually exclusive traits: https://github.com/rust-lang/rust/issues/51774. It seems promising, but it doesn't quite solve my problem.
Is there a way to achieve this behaviour?
trait Operation = Evaluate + (ParametrizedInit or UnparametrizedInit)
How about you use an associated type to define the initialization data?
trait Operation {
type InitData;
fn init(data: Self::InitData) -> Self;
fn evaluate(&self, operand: i32) -> i32;
}
impl Operation for Id {
type InitData = ();
fn init(_: Self::InitData) -> Self {
Id {}
}
fn evaluate(&self, operand: i32) -> i32 {
operand
}
}
impl Operation for Sum {
type InitData = i32;
fn init(augend: Self::InitData) -> Self {
Sum { augend }
}
fn evaluate(&self, addend: i32) -> i32 {
augend + addend
}
}
For the Id case you specify () to say that the initialization does not need data. It's still a bit meh to call Operation::init(()), but I think the trait at least captures the logic fairly well.
To actually get mutually exclusive traits (which is apparently what you want), you have to use some workaround. The Rust language does not support mutually exclusive traits per-se. But you can use associated types and some marker types to get something similar. This is a bit strange, but works for now.
trait InitMarker {}
enum InitFromNothingMarker {}
enum InitFromI32Marker {}
impl InitMarker for InitFromNothingMarker {}
impl InitMarker for InitFromI32Marker {}
trait Operation {
type InitData: InitMarker;
fn init() -> Self
where
Self: Operation<InitData = InitFromNothingMarker>;
fn init_from(v: i32) -> Self
where
Self: Operation<InitData = InitFromI32Marker>;
}
trait UnparametrizedInit: Operation<InitData = InitFromNothingMarker> {}
trait ParametrizedInit: Operation<InitData = InitFromI32Marker> {}
impl<T: Operation<InitData = InitFromNothingMarker>> UnparametrizedInit for T {}
impl<T: Operation<InitData = InitFromI32Marker>> ParametrizedInit for T {}
(Ideally you want to have a Sealed trait that is defined in a private submodule of your crate. That way, no one (except for you) can implement the trait. And then make Sealed a super trait for InitMarker.)
This is quite a bit of code, but at least you can make sure that implementors of Operation implement exactly one of ParametrizedInit and UnparametrizedInit.
In the future, you will likely be able to replace the marker types with an enum and the associated type with an associated const. But currently, "const generics" are not finished enough, so we have to take the ugly route by using marker types. I'm actually discussing these solutions in my master's thesis (section 4.2, just search for "mutually exclusive").
Related
I have a container type like this:
struct Container<T: Sized + MyTrait + Serialize> {
data: T,
key: String
}
wrapping this trait:
trait MyTrait {
fn do_something(&self) -> Something;
}
I have many concrete implementors of MyTrait:
struct S1 {}
impl MyTrait for S1 { ... };
... many kinds ...
struct S10{}
impl MyTrait for S10 { ... }
I have a higher level which should vend the appropriate implementation based on some decision logic (a factory of MyTrait types):
fn get_appropriate_impl(type_of_instance: SomeEnum ... ) -> Container<MyTrait> {
...choose appropriate implementation to return...
return match type_of_instance {
// each branch should return a different type
}
}
I get an error
Container<MyTrait> {
doesn't have a size known at compile-time
I've tried many variations:
Container<Box<MyTrait>>
Box<Container<MyTrait>>
Box<Container<Box<MyTrait>>
each has their own errors
How do I resolve this?
PS - I've also implemented Deref and DerefMut on Container:
impl<T: MyTrait > Deref for Container<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.data
}
}
You are trying to return a trait, not an object. The size of an implementation of a trait is not known at compile time (imagine, you implement MyTrait for u8 and u16, what size the return type would have?). The Sized bound does not help here (and it's implicit, too), because it only requires that T is Sized, but not MyTrait. Also, adding a bound to MyTrait: Sized does not help because it only requires, that the object, you implement MyTrait for, is sized, but it does not specify which size it may have.
This can be solved by either returning a concrete type, which is implementing MyTrait or wrapping the trait (e.g. in a Box). You are probably looking for the latter approach.
Returning a concrete type can be either done by adding a generic argument to the function:
fn get_appropriate_impl<T: MyTrait>() -> Container<T> {
...
}
or by using the impl Trait notation:
fn get_appropriate_impl() -> Container<impl MyTrait> {
...
}
However, if you don't know the return type beforehand, you probably want to store the object on the heap and return a pointer to that, e.g. by using Box, Rc, or Arc:
fn get_appropriate_impl() -> Container<Box<dyn MyTrait>> {
...
}
This requires you to implement MyTrait on Box<dyn MyTrait> as well, this can be done by implementing it on Box yourself:
impl MyTrait for Box<dyn MyTrait> {
fn do_something(&self) -> Something {
(**self).do_something()
}
}
If you know that you will only use Box for Container<T> you might want to encapsulate it there directly:
struct Container {
data: Box<dyn MyTrait>,
}
Suppose I have some custom collection of Foos:
struct Bar {}
struct Foo {
bar: Bar
}
struct SubList {
contents: Vec<Foo>,
}
and suppose I also have a SuperList which is a custom collection of SubLists:
struct SuperList {
contents: Vec<SubList>,
}
SubList and SuperList each provide a method bars:
impl SubList {
fn bars(&self) -> impl Iterator<Item = &Bar> + '_ {
self.contents.iter().map(|x| &x.bar)
}
}
impl SuperList {
fn bars(&self) -> impl Iterator<Item = &Bar> + '_ {
self.contents.iter().flat_map(|x| x.items())
}
}
I want to define a trait that provides a method items, and implement that trait on SubList and SuperList so that SubList::items is equivalent to SubList::bars and SuperList::items is equivalent to SuperList::bars, so that I can do this:
fn do_it<T: Buz<Bar>>(buz: &T) {
for item in buz.items() {
println!("yay!")
}
}
fn main() {
let foos = vec![Foo{ bar: Bar{} }];
let sublist = SubList{ contents: foos };
do_it(&sublist);
let superlist = SuperList{ contents: vec![sublist] };
do_it(&superlist);
}
I can do what I want with dynamic dispatch:
trait Buz<T> {
fn items(&self) -> Box<dyn Iterator<Item = &T> + '_>;
}
impl Buz<Bar> for SubList {
fn items(&self) -> Box<dyn Iterator<Item = &Bar> + '_> {
SubList::bars(self)
}
}
impl Buz<Bar> for SuperList {
fn items(&self) -> Box<dyn Iterator<Item = &Bar> + '_> {
SuperList::bars(self)
}
}
However, the following doesn't work:
trait Baz<T> {
fn items(&self) -> impl Iterator<Item = &T> + '_;
}
impl Baz<Bar> for SubList {
fn items(&self) -> impl Iterator<Item = &Bar> + '_ {
SubList::bars(self)
}
}
impl Baz<Bar> for SuperList {
fn items(&self) -> impl Iterator<Item = &Bar> + '_ {
SuperList::bars(self)
}
}
(error[E0562]: `impl Trait` not allowed outside of function and inherent method return types)
Here's a playground link to what I've tried so far
How can I define a trait Baz which provides an items method to abstract over the bars methods of SubList and SuperList without using dynamic dispatch?
Unfortunately, what you are trying to do is not really possible in Rust right now. Not by design, but simply because some relevant type level features are not implemented or stabilized yet.
Unless you have spare time, an interest in type level stuff and are willing to use nightly: just use boxed iterators. They are simple, they just work, and in most cases it will likely not even hurt performance in a meaningful way.
You're still reading? Ok let's talk about it.
As you intuitively tried, impl Trait in return type position would be the obvious solution here. But as you noticed, it doesn't work: error[E0562]: `impl Trait` not allowed outside of function and inherent method return types. Why is that? RFC 1522 says:
Initial limitations:
impl Trait may only be written within the return type of a freestanding or inherent-impl function, not in trait definitions [...] Eventually, we will want to allow the feature to be used within traits [...]
These initial limitations were put in place because the type level machinery to make this work was/is not in place yet:
One important usecase of abstract return types is to use them in trait methods.
However, there is an issue with this, namely that in combinations with generic trait methods, they are effectively equivalent to higher kinded types. Which is an issue because Rust's HKT story is not yet figured out, so any "accidental implementation" might cause unintended fallout.
The following explanation in the RFC is also worth reading.
That said, some uses of impl Trait in traits can be achieved already today: with associated types! Consider this:
trait Foo {
type Bar: Clone;
fn bar() -> Self::Bar;
}
struct A;
struct B;
impl Foo for A {
type Bar = u32;
fn bar() -> Self::Bar { 0 }
}
impl Foo for B {
type Bar = String;
fn bar() -> Self::Bar { "hello".into() }
}
This works and is "basically equivalent" to:
trait Foo {
fn bar() -> impl Clone;
}
Each impl block can choose a different return type as long as it implements a trait. So why then does impl Trait not simply desugar to an associated type? Well, let's try with your example:
trait Baz<T> {
type Iter: Iterator<Item = &Bar>;
fn items(&self) -> Self::Iter;
}
We get a missing lifetime specifier error:
4 | type Iter: Iterator<Item = &Bar>;
| ^ expected named lifetime parameter
Trying to add a lifetime parameter... we notice that we can't do that. What we need is to use the lifetime of &self here. But we can't get it at that point (in the associated type definition). This limitation is very unfortunate and is encountered in many situations (search term "streaming iterator"). The solution here are GATs: generic associated types. They allow us to write this:
trait Baz<T> {
// vvvv That's what GATs allow
type Iter<'s>: Iterator<Item = &'s Bar>;
fn items(&self) -> Self::Iter<'_>;
}
GATs are not fully implemented and certainly not stable yet. See the tracking issue.
But even with GATs, we cannot fully make your example work. That's because you use iterator types that are unnameable, due to using closures. So the impl Baz for ... blocks would not be able to provide the type Iter<'s> definition. Here we can use another feature that is not stable yet: impl Trait in type aliases!
impl Baz<Bar> for SubList {
type Iter<'s> = impl Iterator<Item = &'s Bar>;
fn items(&self) -> Self::Iter<'_> {
SubList::bars(self)
}
}
This actually works! (Again, on nightly, with these unstable features.) You can see your full, working example in this playground.
This type system work has been going on for a long time and it seems like it's slowly reaching a state of being usable. (Which I am very happy about ^_^). I expect that a few of the foundational features, or at least subsets of them, will be stabilized in the not-too-distant future. And once these are used in practice and we see how they work, more convenience features (like impl Trait in traits) will be reconsidered and stabilized. Or so I think.
Also worth noting that async fns in traits are also blocked by this, since they basically desugar into a method returning impl Future<Output = ...>. And those are also a highly requested feature.
In summary: these limitations have been a pain point for quite some time and they resurface in different practical situations (async, streaming iterator, your example, ...). I'm not involved in compiler development or language team discussions, but I kept an eye on this topic for a long time and I think we are getting close to finally resolving a lot of these issues. Certainly not in the next releases, but I see a decent chance we get some of this in the next year or two.
I am modeling an API where method overloading would be a good fit. My naïve attempt failed:
// fn attempt_1(_x: i32) {}
// fn attempt_1(_x: f32) {}
// Error: duplicate definition of value `attempt_1`
I then added an enum and worked through to:
enum IntOrFloat {
Int(i32),
Float(f32),
}
fn attempt_2(_x: IntOrFloat) {}
fn main() {
let i: i32 = 1;
let f: f32 = 3.0;
// Can't pass the value directly
// attempt_2(i);
// attempt_2(f);
// Error: mismatched types: expected enum `IntOrFloat`
attempt_2(IntOrFloat::Int(i));
attempt_2(IntOrFloat::Float(f));
// Ugly that the caller has to explicitly wrap the parameter
}
Doing some quick searches, I've found some references that talk about overloading, and all of them seem to end in "we aren't going to allow this, but give traits a try". So I tried:
enum IntOrFloat {
Int(i32),
Float(f32),
}
trait IntOrFloatTrait {
fn to_int_or_float(&self) -> IntOrFloat;
}
impl IntOrFloatTrait for i32 {
fn to_int_or_float(&self) -> IntOrFloat {
IntOrFloat::Int(*self)
}
}
impl IntOrFloatTrait for f32 {
fn to_int_or_float(&self) -> IntOrFloat {
IntOrFloat::Float(*self)
}
}
fn attempt_3(_x: &dyn IntOrFloatTrait) {}
fn main() {
let i: i32 = 1;
let f: f32 = 3.0;
attempt_3(&i);
attempt_3(&f);
// Better, but the caller still has to explicitly take the reference
}
Is this the closest I can get to method overloading? Is there a cleaner way?
Yes, there is, and you almost got it already. Traits are the way to go, but you don't need trait objects, use generics:
#[derive(Debug)]
enum IntOrFloat {
Int(i32),
Float(f32),
}
trait IntOrFloatTrait {
fn to_int_or_float(&self) -> IntOrFloat;
}
impl IntOrFloatTrait for i32 {
fn to_int_or_float(&self) -> IntOrFloat {
IntOrFloat::Int(*self)
}
}
impl IntOrFloatTrait for f32 {
fn to_int_or_float(&self) -> IntOrFloat {
IntOrFloat::Float(*self)
}
}
fn attempt_4<T: IntOrFloatTrait>(x: T) {
let v = x.to_int_or_float();
println!("{:?}", v);
}
fn main() {
let i: i32 = 1;
let f: f32 = 3.0;
attempt_4(i);
attempt_4(f);
}
See it working here.
Here's another way that drops the enum. It's an iteration on Vladimir's answer.
trait Tr {
fn go(&self) -> ();
}
impl Tr for i32 {
fn go(&self) {
println!("i32")
}
}
impl Tr for f32 {
fn go(&self) {
println!("f32")
}
}
fn attempt_1<T: Tr>(t: T) {
t.go()
}
fn main() {
attempt_1(1 as i32);
attempt_1(1 as f32);
}
Function Overloading is Possible!!! (well, sorta...)
This Rust Playground example has more a more detailed example, and shows usage of a struct variant, which may be better for documentation on the parameters.
For more serious flexible overloading where you want to have sets of any number of parameters of any sort of type, you can take advantage of the From<T> trait for conversion of a tuple to enum variants, and have a generic function that converts tuples passed into it to the enum type.
So code like this is possible:
fn main() {
let f = Foo { };
f.do_something(3.14); // One f32.
f.do_something((1, 2)); // Two i32's...
f.do_something(("Yay!", 42, 3.14)); // A str, i32, and f64 !!
}
First, define the different sets of parameter combinations as an enum:
// The variants should consist of unambiguous sets of types.
enum FooParam {
Bar(i32, i32),
Baz(f32),
Qux(&'static str, i32, f64),
}
Now, the conversion code; a macro can be written to do the tedious From<T> implementations, but here's what it could produce:
impl From<(i32, i32)> for FooParam {
fn from(p: (i32, i32)) -> Self {
FooParam::Bar(p.0, p.1)
}
}
impl From<f32> for FooParam {
fn from(p: f32) -> Self {
FooParam::Baz(p)
}
}
impl From<(&'static str, i32, f64)> for FooParam {
fn from(p: (&'static str, i32, f64)) -> Self {
FooParam::Qux(p.0, p.1, p.2)
}
}
And then finally, implement the struct with generic method:
struct Foo {}
impl Foo {
fn do_something<T: Into<FooParam>>(&self, t: T) {
use FooParam::*;
let fp = t.into();
match fp {
Bar(a, b) => print!("Bar: {:?}, {:?}\n", a, b),
Baz(a) => print!("Baz: {:?}\n", a),
Qux(a, b, c) => {
print!("Qux: {:?}, {:?}, {:?}\n", a, b, c)
}
}
}
}
Note: The trait bound on T needs to be specified.
Also, the variants need to be composed of combinations of types that the compiler wouldn't find ambiguous - which is an expectation for overloaded methods in other languages as well (Java/C++).
This approach has possibilities... it would be awesome if there's a decorator available - or one were written that did the From<T> implementations automatically when applied to an enum. Something like this:
// THIS DOESN'T EXIST - so don't expect the following to work.
// This is just an example of a macro that could be written to
// help in using the above approach to function overloading.
#[derive(ParameterOverloads)]
enum FooParam {
Bar(i32, i32),
Baz(f32),
Qux(&'static str, i32, f64),
}
// If this were written, it could eliminate the tedious
// implementations of From<...>.
The Builder
Another approach that addresses the case where you have multiple optional parameters to an action or configuration is the builder pattern. The examples below deviate somewhat from the recommendations in the link. Typically, there's a separate builder class/struct which finalizes the configuration and returns the configured object when a final method is invoked.
One of the most relevant situations this can apply to is where you want a constructor that takes a variable number of optional arguments - since Rust doesn't have built-in overloading, we can't have multiple versions of ___::new(). But we can get a similar effect using a chain of methods that return self. Playground link.
fn main() {
// Create.
let mut bb = BattleBot::new("Berzerker".into());
// Configure.
bb.flame_thrower(true)
.locomotion(TractorTreads)
.power_source(Uranium);
println!("{:#?}", bb);
}
Each of the configuration methods has a signature similar to:
fn power_source(&mut self, ps: PowerSource) -> &mut Self {
self.power_source = ps;
self
}
These methods could also be written to consume self and return non-reference copies or clones of self.
This approach can also be applied to actions. For instance, we could have a Command object that can be tuned with chained methods, which then performs the command when .exec() is invoked.
Applying this same idea to an "overloaded" method that we want to take a variable number of parameters, we modify our expectations a bit and have the method take an object that can be configured with the builder pattern.
let mut params = DrawParams::new();
graphics.draw_obj(params.model_path("./planes/X15.m3d")
.skin("./skins/x15.sk")
.location(23.64, 77.43, 88.89)
.rotate_x(25.03)
.effect(MotionBlur));
Alternatively, we could decide on having a GraphicsObject struct that has several config tuning methods, then performs the drawing when .draw() is invoked.
I am not sure that the title of my question is correct since I am not sure where exactly placed.
Let say I have a code which looks like:
struct MyWrapper(u64);
fn my_func<F>(f: F, n: u64) -> MyWrapper
where
F: Fn(u64) -> MyWrapper,
{
f(n)
}
fn main() {
my_func(MyWrapper, 3);
}
It compiles and works so it looks like MyWrapper implements trait Fn.
However, should I try to use it in a trait.
struct MyWrapper(u64);
trait MyTrait
where
Self: Fn(u64) -> MyWrapper,
{
}
impl MyTrait for MyWrapper{}
I get an error
16 | impl MyTrait for MyWrapper{};
| ^^^^^^^ expected an `Fn<(u64,)>` closure, found `MyWrapper`
|
= help: the trait `std::ops::Fn<(u64,)>` is not implemented for `MyWrapper`
It was a more theoretical question.
Speaking practicaly, what I am trying to achieve is to implement trait like this
Edit:
I have rightfully pointed out that my example is not full, so there is a fixed version.
pub enum Status {
New,
Cancelled,
}
struct NewTransaction(u64);
struct CancelledTransaction(u64);
fn get_by_status(id: &str, status: Status) -> Result<u64, ()> {
Ok(3)
}
pub trait Transaction
where
Self: std::marker::Sized,
{
const status: Status;
fn get(id: &str) -> Result<Self, ()>;
}
impl Transaction for NewTransaction {
const status: Status = Status::New;
fn get(id: &str) -> Result<Self, ()> {
get_by_status(id, Self::status).map(Self)
}
}
impl Transaction for CancelledTransaction {
const status: Status = Status::Cancelled;
fn get(id: &str) -> Result<Self, ()> {
get_by_status(id, Self::status).map(Self)
}
}
This code compiles, but as you can see - all implementations of Transaction for every type are exactly the same, so it seems totally reasonable to move this implementation as a default. Like this
pub trait Transaction
where
Self: std::marker::Sized,
{
const status: Status;
fn get(id: &str) -> Result<Self, ()> {
get_by_status(id, Self::status).map(Self)
}
}
impl Transaction for NewTransaction {
const status: Status = Status::New;
}
impl Transaction for CancelledTransaction {
const status: Status = Status::Cancelled;
}
And here I got a complain that Self cannot be used as a Value.
I've tried to fix it by introducing a condition where Self: Fn(u32) -> Self on trait but it didn't work either.
Edit:
In the end, I implemented the idea suggested by Sven Marnach - added a method new and required all struct to implement this method. It still looks quite strange, since the implementation is exactly the same for all structures, but it works.
pub trait Transaction
where
Self: std::marker::Sized,
{
const status: Status;
fn new(n: u64) -> Self;
fn get(id: &str) -> Result<Self, ()> {
get_by_status(id, Self::status).map(Self::new)
}
}
impl Transaction for NewTransaction {
const status: Status = Status::New;
fn new(n: u64) -> Self {
Self(n)
}
}
Thanks everyone for answers!
The constructor of a tuple-like struct or enum variant is actually treated as a function name when used in a context where a value rather than a type is expected, and it is treated as the type it names in a context where a type is expected.
When calling my_func(MyWrapper, 3), the name MyWrapper denotes a function with a function item type that coerces to the function pointer type fn(u64) -> MyWrapper. In particular, the item type implements the trait Fn(u64) -> MyWrapper.
In the code impl MyTrait for MyWrapper {}, however, MyWrapper denotes the struct type it declares. That type is completely different from the type of MyWrapper when used in a value context, and it does not implement the Fn(u64) -> MyWrapper trait.
In your actual use case, I believe the easiest solution is to require a new() method with the desired prototype on the type:
trait Payment {
const status: Status;
fn new(x: u64) -> Self;
fn get(id: u64) -> Result<Self, Error> {
get_by_status(Self::status, id).map(Self::new)
}
}
Implementors of Payment will only need to provide new() method with the desired prototype, but will inherit the default implementation of get().
It compiles and works so it looks like MyWrapper implements trait Fn.
A quick and dirty way to know the type of something in Rust is to do:
struct MyWrapper(u64);
fn main() {
let mut foo = MyWrapper;
foo = ();
}
This produces an error:
error[E0308]: mismatched types
--> src/main.rs:5:11
|
5 | foo = ();
| ^^ expected fn item, found ()
|
= note: expected type `fn(u64) -> MyWrapper {MyWrapper}`
found type `()`
As you can see foo is not a MyWrapper structure so MyWrapper does not implement Fn like you thought it would.
I agree this can be confusing, see tuple struct case:
A struct expression with fields enclosed in parentheses constructs a tuple struct. Though it is listed here as a specific expression for completeness, it is equivalent to a call expression to the tuple struct's constructor. For example:
struct Position(i32, i32, i32);
Position(0, 0, 0); // Typical way of creating a tuple struct.
let c = Position; // `c` is a function that takes 3 arguments.
let pos = c(8, 6, 7); // Creates a `Position` value.
Speaking practically, what I am trying to achieve is to implement trait like this
Your example is not complete so my answer is not final but I don't think it's possible to do what you want.
I don't understand some basics in Rust. I want to compute a function sinc(x), with x being a scalar or a slice, which modifies the values in place. I can implement methods for both types, calling them with x.sinc(), but I find it more convenient (and easier to read in long formulas) to make a function, e.g. sinc(&mut x). So how do you do that properly?
pub trait ToSinc<T> {
fn sinc(self: &mut Self) -> &mut Self;
}
pub fn sinc<T: ToSinc<T>>(y: &mut T) -> &mut T {
y.sinc()
}
impl ToSinc<f64> for f64 {
fn sinc(self: &mut Self) -> &mut Self {
*self = // omitted
self
}
}
impl<'a> ToSinc<&'a mut [f64]> for &'a mut [f64] {
fn sinc(self: &mut Self) -> &mut Self {
for yi in (**self).iter_mut() { ... }
self
}
}
This seems to work, but isn't the "double indirection" in the last impl costly? I also thought about doing
pub trait ToSinc<T> {
fn sinc(self: Self) -> Self;
}
pub fn sinc<T: ToSinc<T>>(y: T) -> T {
y.sinc()
}
impl<'a> ToSinc<&'a mut f64> for &'a mut f64 {
fn sinc(self) -> Self {
*self = ...
self
}
}
impl<'a> ToSinc<&'a mut [f64]> for &'a mut [f64] {
fn sinc(self) -> Self {
for yi in (*self).iter_mut() { ... }
self
}
}
This also works, the difference is that if x is a &mut [f64] slice, I can call sinc(x) instead of sinc(&mut x). So I have the impression there is less indirection going on in the second one, and I think that's good. Am I on the wrong track here?
I find it highly unlikely that any differences from the double-indirection won't be inlined away in this case, but you're right that the second is to be preferred.
You have ToSinc<T>, but don't use T. Drop the template parameter.
That said, ToSinc should almost certainly be by-value for f64s:
impl ToSinc for f64 {
fn sinc(self) -> Self {
...
}
}
You might also want ToSinc for &mut [T] where T: ToSinc.
You might well say, "ah - one of these is by value, and the other by mutable reference; isn't that inconsistent?"
The answer depends on what you're actually intend the trait to be used as.
An interface for sinc-able types
If your interface represents those types that you can run sinc over, as traits of this kind are intended to be used, the goal would be to write functions
fn do_stuff<T: ToSinc>(value: T) { ... }
Now note that the interface is by-value. ToSinc takes self and returns Self: that is a value-to-value function. In fact, even when T is instantiated to some mutable reference, like &mut [f64], the function is unable to observe any mutation to the underlying memory.
In essence, these functions treat the underlying memory as an allocation source, and to value transformations on the data held in these allocations, much like a Box → Box operation is a by-value transformation of heap memory. Only the caller is able to observe mutations to the memory, but even then implementations which treat their input as a value type will return a pointer that prevents needing to access the data in this memory. The caller can just treat the source data as opaque in the same way that an allocator is.
Operations which depend on mutability, like writing to buffers, should probably not be using such an interface. Sometimes to support these cases it makes sense to build a mutating basis and a convenient by-value accessor. ToString is an interesting example of this, as it's just a wrapper over Display.
pub trait ToSinc: Sized {
fn sinc_in_place(&mut self);
fn sinc(mut self) -> Self {
self.sinc_in_place();
self
}
}
where impls mostly just implement sinc_in_place and users tend to prefer sinc.
As fakery for ad-hoc overloading
In this case, one doesn't care if the trait is actually usable generically, or even that it's consistent. sinc("foo") might do a sing and dance, for all we care.
As such, although the trait is needed it should be defined as weakly as possible:
pub trait Sincable {
type Out;
fn sinc(self) -> Self::Out;
}
Then your function is far more generic:
pub fn sinc<T: Sincable>(val: T) -> T::Out {
val.sinc()
}
To implement a by-value function you do
impl Sincable for f64 {
type Out = f64;
fn sinc(self) -> f64 {
0.4324
}
}
and a by-mut-reference one is just
impl<'a, T> Sincable for &'a mut [T]
where T: Sincable<Out=T> + Copy
{
type Out = ();
fn sinc(self) {
for i in self {
*i = sinc(*i);
}
}
}
since () is the default empty type. This acts just like an ad-hoc overloading would.
Playpen example of emulated ad-hoc overloading.