Rust's enums are algebraic datatypes. As far as I can tell this seems to subsume what struct is. What is different about struct that necessitates keeping it?
First of all, you are correct that semantically enum is strictly superior to the struct as to what it can represent, and therefore struct is somewhat redundant.
However, there are other elements at play here.
ease of use: the values within an enum can only be accessed (directly) through matching; contrast with the ease of use of accessing a struct field. You could write accessors for each and every field, but that is really cumbersome.
distinction: an enum is a tagged union, a struct has a fixed-layout; we (programmers) generally like to put labels on things, and therefore giving different names to different functionality can be appreciated.
As I see it, struct is therefore syntactic sugar. I usually prefer lean and mean, but a bit of sugar can go a long way in increasing what can be represented tersely.
Firstly, Rust has a wide array of data types:
Structs with named fields (struct Foo {bar: uint})
Tuple structs (struct Foo(pub Bar, Baz))
Structs with no fields (struct Foo;)
Enums, with various types of variants:
Variants with no fields (eg None)
Tuple variants (eg Some(T))
Struct variants (eg Some { pub inner :T })
This gives the programmer some flexibility in defining datatypes. Often, you don't want named fields, especially if the struct/variant has only one field. Rust lets you use tuple structs/tuple variants in that case.
If structs were removed from Rust there would be no loss of functionality, enums with struct variants could be used again. But there would be an overwhelming number of single-variant enums which would be unnecessary and cumbersome to use.
Not 100% correct, but another nice way to think about it : enum isn't actually superior to struct, the syntax sugar just makes it look like it is.
An enum is a sum type meaning that it's value is one value of one of a set of other types. The Result<T, E> type is either of type T or E. So each enum variant has exactly one type associated with it. Everything else (no type, tuple variants and struct variants) could be syntax sugar.
enum Animal {
// without syntax sugar
Cat(i32),
// desugars to `Dog(())` (empty tuple/unit)
Dog,
// desugars to `Horse((i32, bool))` (tuple)
Horse(i32, bool),
// desugars to `Eagle(GeneratedEagleType)` and a struct definition outside
// of this enum `struct GeneratedEagleType { weight: i32, male: bool }`
Eagle { weight: i32, male: bool }
}
So it would be enough if each enum variant would be associated with exactly one type. And in that case enum is not superior to struct, because it cannot construct product types (like struct).
To be able write the "type definition" inside the enum variant definition is just for convenience.
Also: struct is superior to "tuple structs" and "tuples", too. If we ignore the names those three things are nearly equivalent. But Rust still has those three different kinds of types for convenience.
Please note that I don't know if those enum definitions are actually syntax sugar or not. But they could be and that might help think about. it
Visibility
Not to find reason in what may be a transient implementation detail (I'm not on the core team and have no insight), but
A public enum can not hold or contain a private struct.
A public struct can hold or contain a private enum.
See also
Why does "can't leak private type" only apply to structs and not enums?
You are right that enums and traits and their inheritance by structs both implement algebraic datatypes.
However traits is an extensible set of types, where any stuct can be attributed any trait from any piece of code. Using introspection, it is possible to expect a value with a given trait, and dynamically dig out the actual type of a struct. But that type is among a set of types that is unpredictable, because any struct could be given the said trait from anywhere.
Whereas enums define once and for all a limited type hierarchy, making it predictable and straightforward to go match subtypes as if they were simple values. The language features surrounding enums can therefore be optimized so that the type checks occur statically, providing much greater performance, and some sugar in the language. But also the hierarchy description is contained to the enum definition, and does not impact the trait type system.
TL;DR: enum narrows down a hierarchy of types in a contained manner, instead of relying on traits that are extensible by any piece of code and impacts everything.
Another important distinction along with the above answers is that enum at one point can ONLY be one of the values stated while struct represents values for all the parameters for that instance.
eg.
enum Things{
Box,
Page(i32),
}
struct Things{
box: Option<String>,
page: i32,
}
In the above case a single enum can either be a Box or a Page, while a single instance of struct will represent a box and a page.
Related
I read these docs about structs, but I don't understand about unit structs. It says:
Unit structs are most commonly used as marker. They have a size of zero bytes, but unlike empty enums they can be instantiated, making them isomorphic to the unit type (). Unit structs are useful when you need to implement a trait on something, but don’t need to store any data inside it.
they only give this piece of code as an example:
struct Unit;
What is a real world example of using a unit struct?
Standard library
Global
The global memory allocator, Global, is a unit struct:
pub struct Global;
It has no state of its own (because the state is global), but it implements traits like Allocator.
std::fmt::Error
The error for string formatting, std::fmt::Error, is a unit struct:
pub struct Error;
It has no state of its own, but it implements traits like Error.
RangeFull
The type for the .. operator, RangeFull, is a unit struct:
pub struct RangeFull;
It has no state of its own, but it implements traits like RangeBounds.
Crates
chrono::Utc
The Utc timezone is a unit struct:
pub struct Utc;
It has no state of its own, but it implements traits like TimeZone and is thus usable as a generic argument to Date and DateTime.
In addition to providing a basis for stateless trait implementations, you may also see unit structs created simply to serve as a marker (as mentioned in the quote) to be used in some other structure. Some examples:
In std::marker, there are PhantomData and PhantomPinned which are used to augment other types in concert with the compiler for variance, auto-generated traits, or other behavior. These in particular are special cases and not really a usable pattern outside the standard library.
You can see marker types used in the typestate pattern. Like Rocket<P> in Rocket v0.5. It uses P as simply an indicator of what "pahse" the application is in and provides methods only with specific phases (like can't configure after it is started). Technically Build/Ingite/Orbit are empty structs and not unit structs, but that distinction isn't meaningful here.
You can also see marker types used similarly for defining Format options in the tracing-subscriber crate. The Compact type (a unit struct) is used in conjunction with it to provide different methods and behavior that are not tied to itself directly (Compact implements no traits, and has no methods; the specialization comes from Format<Compact, T> implementing FormatEvent).
Traits are used to group some functions to be implemented from a struct, but is it possible to access struct fields from within the trait?
I could imagine declaring fields inside the trait so that the fields are abstracted as well. I haven't found such a syntax; is there any other solution? Otherwise, it wouldn't be possible to have non-static methods using a trait, would it?
I know object oriented programming from C# and I'm playing around with Rust, trying to adapt the OOP functionality I already know from C#.
This sounds like you're misunderstanding how traits work. Traits can't have fields. If you want to provide access to a field from a trait, you need to define a method in that trait (like, say, get_blah).
If you're asking whether you can access fields of a struct from within that struct's implementation of a trait, then yes. The struct knows it's own type, so there's no problem.
trait Pet {
fn is_smelly(&self) -> bool;
}
struct Dog {
washed_recently: bool,
}
impl Pet for Dog {
fn is_smelly(&self) -> bool {
!self.washed_recently
}
}
If you're writing a default implementation of a trait (i.e. defining a method body within the trait), then no, you can't access fields. A default implementation can only use methods that are defined on the trait or in a super trait.
It would be useful to define fields in a trait's default implementation, so a struct implementing the trait would always have the same fields.
Apparently, the Rust team thinks the same but it is still a work in progress according to this RFC. It's a big change and it has been postponed, so I think the short answer is: you can't do it yet, but you might be able to do it in the future.
For now, you'll have to make do with less powerful traits.
You can make accessor function in default trait implementation, that must return field value/ref in child implementations, returning default value. Use it in other fn's in default implementation, and redefine accessor's in child implementation. Default implementation fn's will use redefined accessors as it's virtual fn's.
This question already has answers here:
Is there any way to restrict a generic type to one of several types?
(2 answers)
Closed 3 years ago.
We can restrict a type to one or more traits by where clause.
My questions are:
Is it possible to restrict a type to just primitive number types?
How?
No.
To use a parametric type, you need the trait to define the valid operations you want to call on it. So you need a trait (or more) with all the operations you want to call.
The “primitive” types in Rust are not special in any way. They define their operators via the traits from std::ops (though obviously using compiler intrinsics) just like any “non-primitive” numberic types.
In fact, the boundary between “primitive” and “non-primitive” numeric types is even somewhat blurred, since for targets that lack an FPU, the standard library may be implementing floating point types in code, and in Rust it can do it transparently to the user.
So really, there is no such thing as primitive number types. Number types are defined by providing whatever operators you need to call. So just restrict your type by the std::ops traits.
Since primitives are not trait types you cannot use as boundary to restrict a generic type. But you can directly implement for a specific type:
struct Struct<T>(T);
trait Printer {
fn print(&self);
}
impl Printer for Struct<i32> {
fn print(&self) {
println!("Printing for i32 value: {}", self.0);
}
}
fn main() {
let x = Struct(15_i32);
let _z = Struct(14.2_f64);
x.print();
//_z.print();//compile error
}
Playground
Alternatively you can use Borrow<S> trait as trick, you can restrict your generic parameter as like below : ( T: Borrow<S> means T can be borrowed as S ) .
impl<T> Printer for Struct<T>
where
T: Borrow<f64> + Debug,
{
fn print(&self) {
println!("Printing for f64 value: {:?}", self.0);
}
}
Playground
But since you can implement Borrow<f64> for any type, this kind of restriction may not be considered as strict.
Also just for the numeric primitives you can use traits from num-traits crate like ToPrimitive AsPrimitive
If you think about traits as compile time duck typing, then a better question would be: what are the exact traits you are looking for in a number? Most of the operations on them could be defined as trait constraints on your types, see: the standard operator traits.
Even if you would define a trait in an external crate and implement the behaviour for specific types only in that external crate, thinking that the trait implementation rules will help you there (i.e. a trait can only be implemented to a type if either the trait or the type or both are in your current crate) would still not limit anyone to implement your trait for their own types.
Therefore, I see no other option but to implement the behaviours without generics for each primitive number type. To avoid code duplication, I would probably use macros for this -- after all, if you think about it in some ways you would manually do what the compiler does while it monomorphises your generic code.
That being said I see no reason to limit behaviours to numbers but to certain traits and I would rely on them as I described it in the first paragraph.
Traits are used to group some functions to be implemented from a struct, but is it possible to access struct fields from within the trait?
I could imagine declaring fields inside the trait so that the fields are abstracted as well. I haven't found such a syntax; is there any other solution? Otherwise, it wouldn't be possible to have non-static methods using a trait, would it?
I know object oriented programming from C# and I'm playing around with Rust, trying to adapt the OOP functionality I already know from C#.
This sounds like you're misunderstanding how traits work. Traits can't have fields. If you want to provide access to a field from a trait, you need to define a method in that trait (like, say, get_blah).
If you're asking whether you can access fields of a struct from within that struct's implementation of a trait, then yes. The struct knows it's own type, so there's no problem.
trait Pet {
fn is_smelly(&self) -> bool;
}
struct Dog {
washed_recently: bool,
}
impl Pet for Dog {
fn is_smelly(&self) -> bool {
!self.washed_recently
}
}
If you're writing a default implementation of a trait (i.e. defining a method body within the trait), then no, you can't access fields. A default implementation can only use methods that are defined on the trait or in a super trait.
It would be useful to define fields in a trait's default implementation, so a struct implementing the trait would always have the same fields.
Apparently, the Rust team thinks the same but it is still a work in progress according to this RFC. It's a big change and it has been postponed, so I think the short answer is: you can't do it yet, but you might be able to do it in the future.
For now, you'll have to make do with less powerful traits.
You can make accessor function in default trait implementation, that must return field value/ref in child implementations, returning default value. Use it in other fn's in default implementation, and redefine accessor's in child implementation. Default implementation fn's will use redefined accessors as it's virtual fn's.
Traits are used to group some functions to be implemented from a struct, but is it possible to access struct fields from within the trait?
I could imagine declaring fields inside the trait so that the fields are abstracted as well. I haven't found such a syntax; is there any other solution? Otherwise, it wouldn't be possible to have non-static methods using a trait, would it?
I know object oriented programming from C# and I'm playing around with Rust, trying to adapt the OOP functionality I already know from C#.
This sounds like you're misunderstanding how traits work. Traits can't have fields. If you want to provide access to a field from a trait, you need to define a method in that trait (like, say, get_blah).
If you're asking whether you can access fields of a struct from within that struct's implementation of a trait, then yes. The struct knows it's own type, so there's no problem.
trait Pet {
fn is_smelly(&self) -> bool;
}
struct Dog {
washed_recently: bool,
}
impl Pet for Dog {
fn is_smelly(&self) -> bool {
!self.washed_recently
}
}
If you're writing a default implementation of a trait (i.e. defining a method body within the trait), then no, you can't access fields. A default implementation can only use methods that are defined on the trait or in a super trait.
It would be useful to define fields in a trait's default implementation, so a struct implementing the trait would always have the same fields.
Apparently, the Rust team thinks the same but it is still a work in progress according to this RFC. It's a big change and it has been postponed, so I think the short answer is: you can't do it yet, but you might be able to do it in the future.
For now, you'll have to make do with less powerful traits.
You can make accessor function in default trait implementation, that must return field value/ref in child implementations, returning default value. Use it in other fn's in default implementation, and redefine accessor's in child implementation. Default implementation fn's will use redefined accessors as it's virtual fn's.