Why can't I access enum fields directly? - rust

I'm just learning Rust.
So I know this works:
enum Animal {
Cat { name: String, weight: f64 }
}
fn main() {
let a = Animal::Cat { name: "Spotty".to_string(), weight: 2.7 };
match a {
Animal::Cat{name, weight} => { println!("Cat n={} w={}", name, weight); }
}
}
but why can I not assign from an enum field directly like this:
...
let wt = a.weight;
Or am I using the wrong syntax? Or is it because Rust cannot guarantee an Animal instance will be of type Cat?
Is the only way to access fields of an instance of enum struct or tuple variant by using match?

It is because weight isn't part of the Animal enum, it's part of the Cat type of the enum. Say in the future you add another type of animal that doesn't have weight information:
enum Animal {
Cat { name: String, weight: f64 },
Insect { species: String, height: f64 },
}
Then a.weight wouldn't always have a value. You can use if let or match to conditionally get data from an enum if it exists:
if let Animal::Cat { weight, .. } = a {
println!("Your cat weights {} kg.", weight);
} else {
println!("get a cat");
};

Or is it because Rust cannot guarantee an Animal instance will be of type Cat?
Yes. In a normal enum, you'd have multiple variants, which may or may not have the same types, and thus fields (in fact some may not have fields at all).
So while it would technically be possible for rust to have attribute access on enums with a single variant or where all variants have a compatible version of the attribute, those are such rare cases they don't really matter and the value of such complexity would be essentially nil.
I don't rightly see the point of using a single-variant enum tho, why are you not just using a struct?
struct Cat { name: String, weight: f64 }
?
Is the only way to access fields of an instance of enum struct or tuple variant by using match?
You could also use an if let though that's essentially the same.
Or you can have a method which exposes the information (with a match or if let inside), as convenience (or because the variants are private).

Related

Rust: Is an Enum a subset of Structs?

I've just learned that an Enum can be initialized with a custom data type.
Wouldn't that make Enums a subset of Structs? An Enum could be a Struct with a single unnamed variable which is initialized once and cannot be changed (like final variables in Java). Also, no methods can be implemented to enums.
Like this:
enum E {
ONE(String)
}
struct S {
one: String
}
Thus, in memory, both a single variable struct and enum would look the same.
Is this true or am I missing something?
It's actually the opposite: structs are subsets of enums. Any struct can be represented as one-variant enum:
struct Record { ... }
struct TupleLike(...);
enum Record { Variant { ... } }
enum TupleLike { Variant(...) }
This enum will even have the same in-memory representation! (though it isn't guaranteed).
On the other hand, enums with multiple variants cannot be described precisely as structs. They are usually implemented as tagged unions, for example:
enum E {
S(String),
I(i32),
}
E::I(123);
type E_Discriminant = u8;
const E_S: E_Discriminant = 0;
const E_I: E_Discriminant = 1;
union E_Payload {
s: String,
i: i32,
}
struct E {
discriminant: E_Discriminant,
payload: E_Payload,
}
E { discriminant: E_I, payload: E_Payload { i: 123 } };
But even doing that manually will not provide you the whole language experience of using enums: you will unable to use pattern matching, accessing variants will be unsafe (and dangerous), etc..
However, when only one variant is needed, structs are more comfortable to use, and that's why they're there.

Rust: Implement Hash/Eq

What is the simplest way to implement Hash/Eq on a struct so that two different instances with the same properties will be unequal?
Consider the following struct:
struct Person {
name: String,
}
What is the simplest way to implement Hash/Eq so that two different people are NOT equal, even if they have the same name? Is the only way to Box<> something?
You don't. Rust isn't Java. Two identical Person instances are represented in memory by the exact same sequence of bits and are, thus, indistinguishable. And Box won't even save you here: The Eq instance for Box delegates to the contained value.
The only way to compare for pointer equality in the way you're describing is with std::ptr::eq, using std::pin to ensure that the pointers don't change. But, and I cannot emphasize this enough, this is the wrong approach. You don't want this kind of equality. It doesn't make sense in Rust. If Person { name: "Joe" } and Person { name: "Joe" } are meant to be distinct objects, then your data structure is poorly designed. It's your job to add a distinguishing field. If these are backed by a database, you might use
struct Person {
primary_key: u64,
name: String,
}
Or maybe everybody has a hexadecimal employee ID.
struct Person {
employee_id: String,
name: String,
}
The point is that the data structure itself (Person in our example) encodes everything about it. Rust eschews the Java-esque notion that every object intrinsically has an identity distinct from all others, in favor of your data explicitly describing itself to the world.
As pointed out by #SilvioMayolo, value "identity" is not a thing in Rust because Rust's values are not heap-allocated by default. While you can take an address of any value, you can't use it as to represent identity the address changes every time the value is moved to a different variable, passed to a function, or inserted in a container. You can make the address stable by heap-allocating the value, but that requires an allocation when the value is created, and an extra dereference on every access.
For values that heap-allocate their content, such as Strings, you could use the address of the contents to represent identity, as shown in #Smitop's answer. But that is also not a good representation of identity because it changes any time the string re-allocates, e.g. if you append some data to it. If you never plan to grow your strings, then that option will work well. Otherwise, you must use something else.
In general, instead of using an address to represent identity, you can explicitly track the identity as part of the object. Nothing stops you from adding a field representing identity, and assigning it in the constructor:
static NEXT_ID: AtomicU64 = AtomicU64::new(0);
pub struct Person {
id: u64,
name: String,
}
impl Person {
/// Create a new unique person.
pub fn new(name: String) -> Self {
Person {
id: NEXT_ID.fetch_add(1, Ordering::Relaxed),
name,
}
}
/// Identity of a `Person`.
///
/// Two persons with the same name will still have different identities.
pub fn id(&self) -> u64 {
self.id
}
}
Then you can implement Hash and Eq to use this identity:
impl Hash for Person {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.id.hash(state);
}
}
impl Eq for Person {}
impl PartialEq for Person {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
// We can't #[derive(Clone)] because that would clone the id, and we
// want to generate a new one instead.
impl Clone for Person {
fn clone(&self) -> Person {
Person::new(self.name.clone())
}
}
Playground
You can compare the String pointers instead of the actual String data. Of course, this method has issue that the Hash of a person will change every execution, but you can't really get around that since there's no extra data in a Person:
use std::{cmp, hash};
impl cmp::PartialEq for Person {
fn eq(&self, other: &Self) -> bool {
self.name.as_ptr() == other.name.as_ptr()
}
}
impl cmp::Eq for Person {}
impl hash::Hash for Person {
fn hash<H: hash::Hasher>(&self, state: &mut H) {
self.name.as_ptr().hash(state);
}
}

Is it possible to bind all the struct members of an enum variant to a single variable?

Let say I define and instantiate an enum as follows:
enum MyEnum {
EmptyVariant,
TupleVariant(u8),
StructVariant {
key: u8,
value: char,
}
}
let instance = MyEnum::StructVariant{key: 8, value: 'a'};
Is it possible to match against this variant without destructuring? For example, instead of doing:
if let MyEnum::StructVariant{key, value} = instance {
eprintln!("key, value = {}, {}", key, value);
}
I would rather write something like:
if let MyEnum::StructVariant{VARIANT_MEMBERS} = instance {
eprintln!("key, value = {}, {}", VARIANT_MEMBERS.key, VARIANT_MEMBERS.value);
}
In this example, writing out the members of the struct variant is benign, but in the case where the variant has many members it makes the code difficult to read.
I don't think it is possible as of today.
I did see the following pattern, which in practice achieves what you asked for just with an extra intermediary type:
Instead place all the desired members of the StructVariant in an actual struct type, and have your enum use that struct as the only field of the StructVariant.
struct MyVariant {
key: u8,
value: char
}
enum MyEnum {
EmptyVariant,
TupleVariant(u8),
StructVariant(MyVariant)
}
if let MyEnum::StructVariant(x) = instance {
eprintln!("key, value = {}, {}", x.key, x.value);
}
This also proves handy when you want to pass the members of StructVariant around, to other functions or types.

How do I define a type hierarchy using Rust enumerations?

I'm trying to make a compiler in Rust, but I'm having problems understanding how to define a type hierarchy using enumerations. We have for example:
enum Thing {
Animal,
Plant,
}
struct Plant {
color: String,
}
enum Animal {
Dog,
Cat,
}
struct Cat {
name: String,
}
struct Dog {
name: String,
}
let x = Dog { name: john };
If I do pattern matching, will Dog be considered of Animal type (or Thing type)? How do I create this type hierarchy using enums and structs? My type hierarchy has many levels of depth.
When you look at this piece of code:
enum Thing {
Animal,
Plant,
}
struct Plant {
color: String,
}
You see the word Plant twice. The important thing to note is that the two Plants are different, unrelated things. Just because they have the same name doesn't mean that they represent the same thing, and in fact they don't really have the same name when you take the fully qualified name: the first one is ::Thing::Plant and the second one is just plain ::Plant.
If you want to link the two, you will need to make it explicit with:
enum Thing {
Animal(Animal),
Plant(Plant),
}
For more details, you can look at the IpAddr example in the Rust book.

Accessing tuple from within an enum

I have a Rust enum defined like this
enum MyFirstEnum {
TupleType(f32, i8, String),
StuctType {varone: i32, vartwo: f64},
NewTypeTuple(i32),
SomeVarName
}
I have the following code:
let mfe: MyFirstEnum = MyFirstEnum::TupleType(3.14, 1, "Hello".to_string());
I'm following the Rust documentation and this looks fine. I don't need to define everything in the enum, but how would I go about accessing the mid element in the enum tuple?
mfe.TupleType.1 and mfe.1 don't work when I add them to a println!
I know Rust provides the facility to do pattern matching to obtain the value, but if I changed the code to define the other variants within the enum, the code to output a particular variant would quickly become a mess.
Is there a simple way to output the variant of the tuple (or any other variant) in the enum?
This is a common misconception: enum variants are not their own types (at least in Rust 1.9). Therefore when you create a variable like this:
let value = MyFirstEnum::TupleType(3.14, 1, "Hello".to_string());
The fact that it's a specific variant is immediately "lost". You will need to pattern match to prevent accessing the enum as the wrong variant. You may prefer to use an if let statement instead of a match:
if let MyFirstEnum::TupleType(f, i, s) = value {
// Values available here
println!("f: {:?}", f);
}
Example solution:
enum MyFirstEnum {
TupleType(f32, i8, String),
// StuctType { varone: i32, vartwo: f64 },
// NewTypeTuple(i32),
// SomeVarName,
}
fn main() {
let mfe: MyFirstEnum = MyFirstEnum::TupleType(3.14, 1, "Hello".to_string());
let MyFirstEnum::TupleType(value, id, text) = &mfe;
println!("[{}; {}; {}]", value, id, text);
//or
match &mfe {
MyFirstEnum::TupleType(value, id, text) => {
println!("[{}; {}; {}]", value, id, text);
}
// _ => {}
}
}
Playground link

Resources