Generalizing a function for an enum - enums

I have an enum that looks like this
pub enum IpNetwork {
V4(Ipv4Network),
V6(Ipv6Network),
}
Each of those variants represents either a IPv4 or v6 CIDR. Now, Ipv4Network and Ipv6Network each has a method to get the prefix defined like this
// For Ipv4Network
pub fn prefix(&self) -> u8
// For Ipv6Network
pub fn prefix(&self) -> u128
How do I generalize the prefix method for the IpNetwork enum? I know that I can just have u128 as the return type, but is that approach idiomatic?

So you want a prefix function that operates on the IpNetwork type, but are unsure what the return type should be. Below is a possible approach you could follow.
The argument against using an enum
As bheklilr mentioned in a comment, one of the alternatives is introducing an enum: pub enum Prefix { V4(u8), V6(u128) }.
This could make sense depending on your use case, but it seems like overkill to me here. In the end, you would end up pattern matching on the result of your generic prefix function. In that case, you could better pattern match on the IpNetwork object itself and call its corresponding prefix function.
The case for u128
If you just want to obtain the integer value and don't need to differentiate between IPV4 and IPV6, returning an integer seems to be the way to go. A u8 can be casted to u128 without any problem and the overhead is negligible.

As far as I know the standard library doesn't hold functionality for generic numeric types. You could, however, define a trait and implement it for u8 and u128.
Also, there is the num crate, which does basically that.

Related

rust: If an enum has all variants derived from a generic struct, what's the best way to inspect the generic stored in the enum?

I've condensed things down to where I have a Generic Adapter, which is stored in an enum, that is non-generic. This looks like:
enum Collective {
StateTable(Adapter<StateTable>),
TestMessage(Adapter<TestMessage>)
}
I have getters and setters for Adapter, and I've stored Collective values in vector. While I can use match, to crack things out, via the following, I'm wondering if there isn't a better way.
impl Collective {
fn get_id(&self) -> u128 {
match self {
ReceiverAdapters::StateTable(a) => a.get_id(),
ReceiverAdapters::TestMessage(a) => a.get_id(),
}
}
I've considered From, but not sure if that will help. I'm also concerned with maintenance, as the number of variants grows. Can anyone provide an alternate to match? Or is match, under the covers compiling down to near-zero code in this case?

How do I pass an enum variant to match on as a function parameter?

I would like to pass in the parameters what arm of the enum I need to match, something like this:
enum D {
A(i64),
B(u64),
C(u64, u64),
}
let a = D.A(10);
println!(a.is_of(D.A)); // true
println!(a.is_of(D.B)); // false
I know I can use matching rules for this, but I'd like this is_of method to take as an input of the enum options for my purposes.
You cannot.
It is not possible to pass types as function parameters.
Enum variants are not types to start with.
If you are OK using a macro instead of a function, see
How do I assert an enum is a specific variant if I don't care about its fields?
The matches crate
See also:
Compare enums only by variant, not value
Can traits be used on enum types?
Can struct-like enums be used as types?
Is there a way to use existing structs as enum variants?

Read from an enum without pattern matching

The Rust documentation gives this example where we have an instance of Result<T, E> named some_value:
match some_value {
Ok(value) => println!("got a value: {}", value),
Err(_) => println!("an error occurred"),
}
Is there any way to read from some_value without pattern matching? What about without even checking the type of the contents at runtime? Perhaps we somehow know with absolute certainty what type is contained or perhaps we're just being a bad programmer. In either case, I'm just curious to know if it's at all possible, not if it's a good idea.
It strikes me as a really interesting language feature that this branch is so difficult (or impossible?) to avoid.
At the lowest level, no, you can't read enum fields without a match1.
Methods on an enum can provide more convenient access to data within the enum (e.g. Result::unwrap), but under the hood, they're always implemented with a match.
If you know that a particular case in a match is unreachable, a common practice is to write unreachable!() on that branch (unreachable!() simply expands to a panic!() with a specific message).
1 If you have an enum with only one variant, you could also write a simple let statement to deconstruct the enum. Patterns in let and match statements must be exhaustive, and pattern matching the single variant from an enum is exhaustive. But enums with only one variant are pretty much never used; a struct would do the job just fine. And if you intend to add variants later, you're better off writing a match right away.
enum Single {
S(i32),
}
fn main() {
let a = Single::S(1);
let Single::S(b) = a;
println!("{}", b);
}
On the other hand, if you have an enum with more than one variant, you can also use if let and while let if you're interested in the data from a single variant only. While let and match require exhaustive patterns, if let and while let accept non-exhaustive patterns. You'll often see them used with Option:
fn main() {
if let Some(x) = std::env::args().len().checked_add(1) {
println!("{}", x);
} else {
println!("too many args :(");
}
}

What does the 'where' clause within a trait do?

If I have this code:
trait Trait {
fn f(&self) -> i32 where Self: Sized;
fn g(&self) -> i32;
}
fn object_safety_dynamic(x: &Trait) {
x.f(); // error
x.g(); // works
}
What does the where clause actually do?
Naively, I was thinking where Self: Sized; dictates something about the type implementing Trait, like 'if you implement Trait for type A your type A must be sized, i.e., it can be i32 but not [i32].
However, such a constraint would rather go as trait Trait: Sized (correct me if I am wrong)?
Now I noticed where Self: Sized; actually determines if I can call f or g from within object_safety_dynamic.
My questions:
What happens here behind the scenes?
What (in simple English) am I actually telling the compiler by where Self: Sized; that makes g() work but f() not?
In particular: Since &self is a reference anyway, what compiled difference exists between f and g for various (sized or unsized) types. Wouldn't it always boil down to something like _vtable_f_or_g(*self) -> i32, regardless of where or if the type is sized or not?
Why can I implement Trait for both u8 and [u8]. Shouldn't the compiler actually stop me from implementing f() for [u8], instead of throwing an error at the call site?
fn f(&self) -> i32 where Self: Sized;
This says that f is only defined for types that also implement Sized. Unsized types may still implement Trait, but f will not be available.
Inside object_safety_dynamic, calling x.f() is actually doing: (*x).f(). While x is sized because it's a pointer, *x might not be because it could be any implementation of Trait. But code inside the function has to work for any valid argument, so you are not allowed to call x.f() there.
What does the where clause actually do?
Naively, I was thinking where Self: Sized; dictates something about the type implementing Trait, like 'if you implement Trait for type A your type A must be sized, i.e., it can be i32 but not [i32].
However, such a constraint would rather go as trait Trait: Sized
This is correct.
However, in this case, the bound applies only to the function. where bounds on functions are only checked at the callsite.
What happens here behind the scenes?
There is a confusing bit about rust's syntax which is that Trait can refer to either
The trait Trait; or
The "trait object" Trait, which is actually a type, not an object.
Sized is a trait, and any type T that is Sized may have its size taken as a constant, by std::mem::size_of::<T>(). Such types that are not sized are str and [u8], whose contents do not have a fixed size.
The type Trait is also unsized. Intuitively, this is because Trait as a type consists of all values of types that implement the trait Trait, which may have varying size. This means you can never have a value of type Trait - you can only refer to one via a "fat pointer" such as &Trait or Box<Trait> and so on. These have the size of 2 pointers - one for a vtable, one for the data. It looks roughly like this:
struct &Trait {
pub data: *mut (),
pub vtable: *mut (),
}
There is automatically an impl of the form:
impl Trait /* the trait */ for Trait /* the type */ {
fn f(&self) -> i32 where Self: Sized { .. }
fn g(&self) -> i32 {
/* vtable magic: something like (self.vtable.g)(self.data) */
}
}
What (in simple English) am I actually telling the compiler by where Self: Sized; that makes g() work but f() not?
Note that since, as I mentioned, Trait is not Sized, the bound Self: Sized is not satisfied and so the function f cannot be called where Self == Trait.
In particular: Since &self is a reference anyway, what compiled difference exists between f and g for various (sized or unsized) types. Wouldn't it always boil down to something like _vtable_f_or_g(*self) -> i32, regardless of where or if the type is sized or not?
The type Trait is always unsized. It doesn't matter which type has been coerced to Trait. The way you call the function with a Sized variable is to use it directly:
fn generic<T: Trait + Sized>(x: &T) { // the `Sized` bound is implicit, added here for clarity
x.f(); // compiles just fine
x.g();
}
Why can I implement Trait for both u8 and [u8]. Shouldn't the compiler actually stop me from implementing f() for [u8], instead of throwing an error at the call site?
Because the trait is not bounded by Self: Sized - the function f is. So there is nothing stopping you from implementing the function - it's just that the bounds on the function can never be satisfied, so you can never call it.

Cannot use Rayon's `.par_iter()`

I have a struct which implements Iterator and it works fine as an iterator. It produces values, and using .map(), I download each item from a local HTTP server and save the results. I now want to parallelize this operation, and Rayon looks friendly.
I am getting a compiler error when trying to follow the example in the documentation.
This is the code that works sequentially. generate_values returns the struct which implements Iterator. dl downloads the values and saves them (i.e. it has side effects). Since iterators are lazy in Rust, I have put a .count() at the end so that it will actually run it.
generate_values(14).map(|x| { dl(x, &path, &upstream_url); }).count();
Following the Rayon example I tried this:
generate_values(14).par_iter().map(|x| { dl(x, &path, &upstream_url); }).count();
and got the following error:
src/main.rs:69:27: 69:37 error: no method named `par_iter` found for type `MyIterator` in the current scope
Interestingly, when I use .iter(), which many Rust things use, I get a similar error:
src/main.rs:69:27: 69:33 error: no method named `iter` found for type `MyIterator` in the current scope
src/main.rs:69 generate_values(14).iter().map(|tile| { dl_tile(tile, &tc_path, &upstream_url); }).count();
Since I implement Iterator, I should get .iter() for free right? Is this why .par_iter() doesn't work?
Rust 1.6 and Rayon 0.3.1
$ rustc --version
rustc 1.6.0 (c30b771ad 2016-01-19)
Rayon 0.3.1 defines par_iter as:
pub trait IntoParallelRefIterator<'data> {
type Iter: ParallelIterator<Item=&'data Self::Item>;
type Item: Sync + 'data;
fn par_iter(&'data self) -> Self::Iter;
}
There is only one type that implements this trait in Rayon itself: [T]:
impl<'data, T: Sync + 'data> IntoParallelRefIterator<'data> for [T] {
type Item = T;
type Iter = SliceIter<'data, T>;
fn par_iter(&'data self) -> Self::Iter {
self.into_par_iter()
}
}
That's why Lukas Kalbertodt's answer to collect to a Vec will work; Vec dereferences to a slice.
Generally, Rayon could not assume that any iterator would be amenable to parallelization, so it cannot default to including all Iterators.
Since you have defined generate_values, you could implement the appropriate Rayon trait for it as well:
IntoParallelIterator
IntoParallelRefIterator
IntoParallelRefMutIterator
That should allow you to avoid collecting into a temporary vector.
No, the Iterator trait has nothing to do with the iter() method. Yes, this is slightly confusing.
There are a few different concepts here. An Iterator is a type that can spit out values; it only needs to implement next() and has many other methods, but none of these is iter(). Then there is IntoIterator which says that a type can be transformed into an Iterator. This trait has the into_iter() method. Now the iter() method is not really related to any of those two traits. It's just a normal method of many types, that often works similar to into_iter().
Now to your Rayon problem: it looks like you can't just take any normal iterator and turn it into a parallel one. However, I never used this library, so takes this with a grain of salt. To me it looks like you need to collect your iterator into a Vec to be able to use par_iter().
And just as a note: when using normal iterators, you shouldn't use map() and count(), but rather use a standard for loop.

Resources