Imagine I have functions like this:
fn min_max_difference(row: &Vec<u32>) -> u32 {
let mut min_elem: u32 = row[0];
let mut max_elem: u32 = min_elem;
for &element in row.iter().skip(1) {
if element < min_elem {
min_elem = element;
} else if element > max_elem {
max_elem = element;
}
}
result = max_elem - min_elem;
}
fn execute_row_operation(row: &Vec<u32>, operation: Fn(&Vec<u32>) -> u32) -> Option<(u32, u32)> {
let mut result = None;
if row.len() > 0 {
result = operation(row);
}
result
}
Note that the if
block in execute_row_operation
guarantees that the Vec<u32>
I am passing to the operation
function is non-empty. In general, I want "operations" to be functions which only accept non-empty rows. I would like it if I could do something like this:
fn min_max_difference<T: &Vec<u32> + NonEmpty>(row: T) -> u32 {
//snip
}
This would allow the compiler to disallow passing references to empty vectors to a function like min_max_difference
which expects this.
But traits as I understand them specify what methods a type has, rather than what properties a type has. In my head, I am imagining a trait for a type T
that is composed of boolean predicates with type: Fn<T> -> bool
, and such a trait is "implemented" for a type if it all those predicates evaluate to true.
Can something like this be achieved?
Can a trait guarantee certain type properties
Yes, that is what they are for. In many cases, these properties are that a set of functions exist (e.g. PartialEq::eq
) and that a set of behaviors are present (e.g. symmetric and transitive equality, required by PartialEq
).
Traits can also have no methods, such as Eq
. These only add a set of behaviors (e.g. reflexive equality). These types of traits are often referred to as marker traits.
such as a vector is non-empty?
However, you aren't asking for what you really want. You actually want a way to implement a trait for certain values of a type. This is not possible in Rust.
At best, you can introduce a newtype. This might be sufficient for your needs, but you could also implement your own marker traits for that newtype, if useful:
struct NonEmptyVec<T>(Vec<T>);
impl<T> NonEmptyVec<T> {
fn new(v: Vec<T>) -> Result<Self, Vec<T>> {
if v.is_empty() {
Err(v)
} else {
Ok(NonEmptyVec(v))
}
}
}
fn do_a_thing<T>(items: NonEmptyVec<T>) {}
fn main() {
let mut a = Vec::new();
// do_a_thing(a); // expected struct `NonEmptyVec`, found struct `std::vec::Vec`
a.push(42);
let b = NonEmptyVec::new(a).expect("nope");
do_a_thing(b);
}
T: &Vec<u32> + NonEmpty
This isn't valid because Vec
is a type and NonEmpty
would presumably be a trait — you can't use types as trait bounds.
Historical note:
Way back in the long ago, as I understand it, Rust actually did support what you wanted under the name typestate. See What is typestate? and Typestate Is Dead, Long Live Typestate!.
An example of emulating it:
struct MyVec<T, S>
where
S: VecState,
{
vec: Vec<T>,
state: S,
}
trait VecState {}
struct Empty;
struct NonEmpty;
impl VecState for Empty {}
impl VecState for NonEmpty {}
impl<T> MyVec<T, Empty> {
fn new() -> Self {
MyVec {
vec: Vec::new(),
state: Empty,
}
}
fn push(mut self, value: T) -> MyVec<T, NonEmpty> {
self.vec.push(value);
MyVec {
vec: self.vec,
state: NonEmpty,
}
}
}
fn do_a_thing<T>(items: MyVec<T, NonEmpty>) {}
fn main() {
let a = MyVec::new();
// do_a_thing(a); // expected struct `NonEmpty`, found struct `Empty`
let b = a.push(42);
do_a_thing(b);
}