Equivalent to this c++ constraint?

Hi,
I am in a situation where I read more and more Rust code to complete/replace(not sure) C++.
I need to constraint unrelated generic types. I think you call it Bounds.
To make sure to get the help from the compiler, I restrict some generic functions to a handful of types.

Do Rust has this equivalent:

template <class T>
concept Check_vector_types = ( common_with <T, VectorT1> ) or ( common_with <T, VectorT2> );

and used like:

template <Check_vector_types T>
                auto my_function ( T &&vect )->void {}
or 
template <Check_vector_types T>
                auto my_function ( T &vect )->void {}
or 
template <Check_vector_types T>
                auto my_function ( T vect )->void {}

In my_function, T can only be VectorT1, VectorT2 or associated types (refs because we don't use pointers).
In Rust, there is the where clause but as I get it, there is only a '+' to add constraints but not a 'or' to alternate types.

So something like that is what I need:

where T: VectorT1 || VectorT2 (not sure if it handles refs as well)

So is there an equivalent construct that avoid to completely change the logic?

Thanks

You can't have "trait bounds" with concrete types (they are "trait bounds"). What you can do is either define your own trait you only implement for those few concrete types or just implement your generic for only those types directly.

4 Likes

Ok, sounds like a lot of typing/ duplicate code to achieve this goal, no?

Not really IMO. Here's a playground showing the two methods. Note that putting trait bounds on the struct definition is not considered good practice, I did it here for expediency, and also the 2nd method can make good use of really simple macros.

1 Like

Hmmm.. I get it. I feel like working for the compiler.. we will see..
Thanks :+1:

Is there any reason why you think you need to constrain the functions to just a set of specific types? That is usually a code smell, and not something the language should strive for supporting.

Usually, if you want a function that works over a set of similarly-behaving types, you should accept a generic type parameter, and not "guess" or enumerate the concrete types.

10 Likes

Yeah that's my thought as well. We need more information about the problem you're trying to solve and why you need to limit a function to a closed set of types. If using traits, a sealed trait could also help, but it is a bit of a hack

And if you do want to enumerate a closed set of types, perhaps consider the language feature explicitly designed for enumeration.

Or, indeed, just have two different named functions.

The way you design stuff in Rust will be different than C++. You have to take the whole system into consideration. You can't start with something that works in C++ and just kind of nudge it into shape. (Well, you can, but you'll probably end up with holes in your design named "inheritance" and "SFINAE" etc. because Rust offers no particular solution that fits exactly what C++ offers.)

4 Likes

I get it.
C++ is used without OOP. Just like C with more batteries. Like Rust in a sense. Hence my interest.
We do that for all the things it brings on the table and the productivity bump. No time to reinvent the wheel.

The use case here is to enforce only some types to enter that function.
Seems odd at first but given it is in AI field, sometimes, it is very important to make sure you didn't forget a condition that could silently infer bugs down the road. And some classes of ours will.

It is just that: a slight help because one could forget a condition and this enforces it. And it is a good thing when multiple talents, litteraly, work on the same subject. Any help a machine can give, we take.

Honestly, a simple notation like where T: type1 | type2 is just what I would have hoped/needed.

I understand the fact that they are two languages, but we have practical needs to fulfill today, not tomorrow.

In that case, what you want is an enum.

3 Likes

Ha yes, this is exactly what I need.

Thanks!

1 Like

One possible pattern which can allow you to provide API similar to the C++ one is to implement a classifying enum together with From-conversions from the contained types. Afterwards the generic function can accept anything which can be converted into that enum:

pub struct Vec1 { /* snip */ }

pub struct Vec2 { /* snip */ }

pub enum VecLike {
    Vec1(Vec1),
    Vec2(Vec2),
}

impl From<Vec1> for VecLike {
    fn from(val: Vec1) -> Self { VecLike::Vec1(val) }
}

impl From<Vec2> for VecLike {
    fn from(val: Vec2) -> Self { VecLike::Vec2(val) }
}

pub fn foo<V: Into<VecLike>>(v: V) {
    match v.into() {
        VecLike::Vec1(v1) => { /* do stuff with Vec1 */ }
        VecLike::Vec2(v2) => { /* do stuff with Vec2 */ }
    }
}

Usage:

pub fn bar(v: Vec1) {
    foo(v);
}

pub fn baz(v: Vec2) {
    foo(v);
}

This API is slightly smelly and, honestly, I would avoid structuring it like this in my own code, but occasionally it is useful. The better approach would be, indeed, to introduce a trait which describes the specific behaviour you need to use in foo, and implement this trait for the admissible types. If you really want to keep the set of implementations restricted to the ones blessed by you, you can use the sealed trait pattern to prevent downstream users from implementing it. Again, this is something which you should usually avoid, but occasionally it's the best solution.

Note while C++ templates are duck-typed, Rust generics are nominally typed, which gives strong control of the admissible parameter types and the guarantees they provide.

1 Like

Thanks afetisov.

Is it possible to go away with the impl and embed the implementation directly inside foo?

I don't understand your question. Are you asking whether the impl blocks can be moved inside foo? Yes, an impl can be declared anywhere, but why would you do that? If you're asking something else, then please state your question differently.