Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 5 years ago.
Improve this question
I have been wondering about different ways of instantiating structs I came across in Rust so far. So there is the most basic/simple way of setting all the fields manually when everything is public:
let a = Structure { arg1: T, arg2: T, ... }
When there is a need for privacy and better interface and/or defaults, it's common to use 'contructors' such us new(), etc:
let a = Structure::new(arg1, arg2, ...)
Now, so far it kind of makes sens to me. However there seems to be a third common way of doing the same which confuses me the most. Here is a concrete example:
let mut image_file = OpenOptions::new()
.write(true)
.truncate(true)
.create(true)
.open(file_path)
.unwrap();
So my questions are:
What are the performance impact of these different solutions ( if any )?
What are general benefits and disadvantages of each?
Are there more ways of doing the same?
Which is the best practice?
You have identified 3 ways to create a struct:
Direct: directly initializing its fields,
Constructor: calling a single function which initializes the struct,
Builder: assembling the struct elements piece-meal then finally initializing a struct.
Are there more ways of doing the same?
Direct initialization has two variations: either initialing each field directly, or initializing a few fields and "defaulting" the others with struct S { f0, .. OTHERS } where OTHERS is an instance of S.
The Constructor and Builder ways have a exponential number of variations, depending on how you group the parameters, and in some instances the line between the two will be blurry.
All ways, however, must at some point converge and use (1) to create an instance of S.
What are general benefits and disadvantages of each?
This is... irrelevant, to some extent.
Each of the 3 alternatives caters to a different set of needs:
Direct initialization requires accessible fields; since pub fields are rare it is therefore mostly used within the crate but not usable by clients.
Constructor and Builder allow establishing invariants and are therefore the primary client's interface.
The Constructor is simple but inflexible: no new parameter can be added without breaking backward compatibility (another Constructor can, of course); the Builder on the other hand is flexible, at the cost of verbosity.
What are the performance impact of these different solutions ( if any )?
Ideally, in an optimized binary, both Constructor and Builder should have the same cost. If it matters, profile.
Direct initialization will be faster than either if they establish invariants, as it does not. Comparing the performance of non-equivalent functionality rarely matters though.
Which is the best practice?
Avoid Direct Initialization.
Direct Initialization does NOT establish invariants, it's up to the surrounding code to establishing them, which therefore means that any time Direct Initialization is used the invariant checking code is duplicated, which violates the DRY principle.
Direct Initialization also goes against encapsulation, preventing any further change of the underlying structure, down to the type of the fields used. This is generally undesirable.
There are exceptions, as always. The most prominent being that implementing the Constructor or Builder requires using Direct Initialization down the road.
Choosing between Constructor and Builder is more subjective. In general, I recommend a Constructor when the parameters are few, even if this means writing a few of them, such as Vec::{new, with_capacity}. When the number of Constructors would get out of hand if one needed to write one for each combination of parameters which makes sense, then use a Builder instead.
Related
I'm having problem understanding the usefulness of Rust enums after reading The Rust Programming Language.
In section 17.3, Implementing an Object-Oriented Design Pattern, we have this paragraph:
If we were to create an alternative implementation that didn’t use the state pattern, we might instead use match expressions in the methods on Post or even in the main code that checks the state of the post and changes behavior in those places. That would mean we would have to look in several places to understand all the implications of a post being in the published state! This would only increase the more states we added: each of those match expressions would need another arm.
I agree completely. It would be very bad to use enums in this case because of the reasons outlined. Yet, using enums was my first thought of a more idiomatic implementation. Later in the same section, the book introduces the concept of encoding the state of the objects using types, via variable shadowing.
It's my understanding that Rust enums can contain complex data structures, and different variants of the same enum can contain different types.
What is a real life example of a design in which enums are the better option? I can only find fake or very simple examples in other sources.
I understand that Rust uses enums for things like Result and Option, but those are very simple uses. I was thinking of some functionality with a more complex behavior.
This turned out to be a somewhat open ended question, but I could not find a useful response after searching Google. I'm free to change this question to a more closed version if someone could be so kind as to help me rephrase it.
A fundamental trade-off between these choices in a broad sense has a name: "the expression problem". You should find plenty on Google under that name, both in general and in the context of Rust.
In the context of the question, the "problem" is to write the code in such a way that both adding a new state and adding a new operation on states does not involve modifying existing implementations.
When using a trait object, it is easy to add a state, but not an operation. To add a state, one defines a new type and implements the trait. To add an operation, naively, one adds a method to the trait but has to intrusively update the trait implementations for all states.
When using an enum for state, it is easy to add a new operation, but not a new state. To add an operation, one defines a new function. To add a new state, naively, one must intrusively modify all the existing operations to handle the new state.
If I explained this well enough, hopefully it should be clear that both will have a place. They are in a way dual to one another.
With this lens, an enum would be a better fit when the operations on the enum are expected to change more than the alternatives. For example, suppose you were trying to represent an abstract syntax tree for C++, which changes every three years. The set of types of AST nodes may not change frequently relative to the set of operations you may want to perform on AST nodes.
With that said, there are solutions to the more difficult options in both cases, but they remain somewhat more difficult. And what code must be modified may not be the primary concern.
Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 2 years ago.
Improve this question
I am building a simulation (the coding equivalent of a model train set). It is a simulated economy with various economic agents interacting with each other. The main mode of interaction between economic agents is a transaction. At each "tic", each agent generates a list of zero or more proposed transactions (such as buying food). At each "toc" all the counter-parties process the proposed transactions that have been targeted at them in random order so that no biases are introduced. In these snippets a proposed transaction is represented as a u32.
My goal is to simulate as many of these economic agents as possible so performance is key. I am new to rust (or any kind of low level language for that matter) and my understanding from reading the rust book is if I want maximum performance then use "zero cost abstractions" and avoid dynamic dispatch.
So with that out the way I have come up with the following 3 approaches.
Option 1
trait EconomicAgent {
fn proposed_transactions(&self) -> Vec<u32>;
}
struct Person {
health:f64,
energy:f64,
nutrition:f64,
money:f64,
food:f64
}
impl EconomicAgent for Person {
fn proposed_transactions(&self) -> Vec<u32> {
vec![1, 2, 3]
}
}
struct FoodStore {
money:f64,
food:f64
}
impl EconomicAgent for FoodStore {
fn proposed_transactions(&self) -> Vec<u32> {
vec![4, 5, 6]
}
}
A person and a food store are different types that implement the EconomicAgent trait. I can then iterate over a vector of trait objects to get a list of proposed transactions. Each call is dynamically dispatched, I believe.
Option 2
enum EconomicAgent2 {
Person(Person),
FoodStore(FoodStore)
}
impl EconomicAgent2 {
fn proposed_transactions(&self) -> Vec<u32> {
match self{
EconomicAgent2::Person(person) => person.proposed_transactions(),
EconomicAgent2::FoodStore(food_store) => food_store.proposed_transactions()
}
}
}
Here, an EconomicAgent is not a trait, but rather an enum and, well you can see how it works.
Option 3
const HEALTH_INDEX : u8 = 0;
const ENERGY_INDEX : u8 = 1;
const NUTRITION_INDEX : u8 = 2;
const MONEY_INDEX : u8 = 3;
const FOOD_INDEX : u8 = 4;
enum EconomicAgentTag {
Person,
FoodStore
}
struct EconomicAgent3 {
tag: EconomicAgentTag,
resources:[f64; 5],
proposed_transactions: Box<fn(&EconomicAgent3) -> Vec<u32>>
}
fn new_person() -> EconomicAgent3 {
EconomicAgent3 {
tag: EconomicAgentTag::Person,
resources: [0.0,0.0,0.0,0.0,0.0],
proposed_transactions: Box::new(|_| vec![1, 2, 3])
}
}
fn new_food_Store() -> EconomicAgent3 {
EconomicAgent3 {
tag: EconomicAgentTag::FoodStore,
resources: [0.0,0.0,0.0,0.0,0.0],
proposed_transactions: Box::new(|_| vec![4, 5, 6])
}
}
Here an economic agent is more abstract representation.
Now imagine that there a many different types of economic agents (banks, mines, farms, clothing stores etc). They all interact by proposing and accepting transactions. Option 1 seems to suffer from dynamic dispatch. Option 2 seems to be my own version of dynamic dispatch via a match expression so is probably no better, right? Option 3 seems like it should be the most performant but does not really allow much cognitive ease on the part of the programmer.
So finally the questions:
Clearly dynamic dispatch is involved in option 1. What about options 2 and 3?
Which is expected to be most performant? Note I am not really in a position to do testing as the full idea (only on paper right now) is obviously more complex than these snippets and the choice now will affect the entire structure for the whole project.
What would be an idiomatic choice here?
All your options use dynamic dispatch or branches in one way or another to call the right function for each element. The reason is that you are mixing all the agents into a single place, which is where the different performance penalties come from (not just the indirect calls or branches, but also cache misses etc.).
Instead, for a problem like this, you want to separate the different "agents" into separate, independent "entities". Then, to reuse code, you will want to factor out "components" for which subsets of them are iterated by "systems".
This is what is usually called an "Entity-Component-System" (ECS) of which there are many models and implementations. They are typically used by games and other simulations.
If you search for ECS you will find many questions, articles and so on about it and the different approaches.
What is Dynamic Dispatch?
Dynamic Dispatch is usually reserved to indirect function calls, ie function calls which occurs via a function pointer.
In your case, both Option 1 and Option 3 are cases of dynamic dispatch:
Traits use a virtual table, which is a table of function pointers.
fn(...) -> ... is a function pointer.
What is the performance penalty of Dynamic Dispatch?
At run-time, there is little to no difference between a regular function call and a so-called virtual call:
Indirect function calls can be predicted, there's a special predictor for them in your CPU.
The cost of the function call is mostly saving/restoring registers, which happen in both cases.
The performance penalty is more insidious, it happens at compile-time.
The mother of optimizations is inlining, which essentially copy/paste the body of the function being called right at the call-site. Once a function is inlined, many other optimization passes can go to town on the (combined) code. This is especially lucrative on very small functions (a getter), but can also be quite beneficial on larger functions.
An indirect function call, however, is opaque. There are many candidate functions, and thus the optimizer cannot perform inlining... nipping many potential optimizations in the bud. Devirtualization is sometimes available -- the compiler deducing which function(s) can be called -- but should not be relied on.
Which Option to choose?
Among those presented: Option 2!
The main advantage of Option 2 is that there is no indirect function calls. In both branches of your match, the compiler has a known type for the receiver of the method and can therefore inline the method if suitable, enabling all the optimizations.
Is there better?
With an open-design, an Array of Structs is a better way to structure the system, mostly avoiding branch misprediction:
EconomicAgents {
Person(Vec<Person>),
FoodStore(Vec<FoodStore>),
}
This is the core design of the ECS solution proposed by #Acorn.
Note: as noted by #Acorn in comments, Array of Structs also is close to optimal cache wise -- no indirection, very little padding between elements.
Going with a full ECS is a trickier proposition. Unless you have dynamic entities -- Person/FoodStores are added/removed during the runs -- I would not bother. ECS are helpful for dynamism, but have to choose a trade-off between various characteristics: do you want faster add/remove, or faster iteration? Unless you need all their features, they will likely add their own overhead due to trade-offs that do not match your needs.
How to avoid dynamic dispatch?
You could go with option 1 and instead of having vector of trait objects, keep each type in its own vector and iterate them individually. It is not nice solution, so...
Instead...
Choose whichever option allows you to model your simulation best and don't worry about the const of dynamic dispatch. The overhead is small. There are other things that will impact the performance more, such as allocating new Vec for every call.
The main cost of dynamic dispatch is indirect branch predictor making wrong guess. To help your cpu make better guesses, you may try to keep objects of the same type next to each other in the vector. For example sort it by type.
Which one is idiomatic?
The option 1 has an issue that to store objects of different types into vector, you must go thru indirection. The easiest is to Box every object, but that means every access will have not only dynamic dispatch for the function, but will also have to follow extra pointer to get to the data.
The option 2 with enum is (in my opinion) more idiomatic - you have all your data together in continuous memory. But beware that the size of an enum is bigger (or equal) to its largest variant. So if you agents vary in size, it may be better to go with option 1.
Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 4 years ago.
Improve this question
In Chapter 3 of The Rust Programming Language, the following code is used as an example for a kind of type inference that Rust cannot manage:
fn main() {
let condition = true;
let number = if condition { 5 } else { "six" };
println!("The value of number is: {}", number);
}
with the explanation that:
Rust needs to know at compile time what type the number variable is, definitively, so it can verify at compile time that its type is valid everywhere we use number. Rust wouldn’t be able to do that if the type of number was only determined at runtime; the compiler would be more complex and would make fewer guarantees about the code if it had to keep track of multiple hypothetical types for any variable.
I'm not certain I understand the rationale, because the example does seem like something where a simple compiler could infer the type.
What exactly makes this kind of type inference so difficult? In this case, the value of condition can be clearly inferred at compile time (it's true), and so thus the type of number can be too (it's i32?).
I can see how things could become a lot more complicated, if you were trying to infer types across multiple compilation units for instance, but is there something about this specific example that would add a lot of complexity to the compiler?
There are three main reasons I can think of:
1. Action at a distance effects
Let's suppose the language worked that way. Since we're extending type inference, we might as well make the language even smarter and have it infer return types as well. This allows me to write something like:
pub fn get_flux_capacitor() {
let is_prod = true;
if is_prod { FluxCapacitor::new() } else { MovieProp::new() }
}
And elsewhere in my project, I can get a FluxCapacitor by calling that function. However, one day, I change is_prod to false. Now, instead of getting an error that my function is returning the wrong type, I will get errors at every callsite. A small change inside one function has lead to errors in entirely unchanged files! That's pretty weird.
(If we don't want to add inferered return types, just imagine it's a very long function instead.)
2. Compiler internals exposed
What happens in the case where it's not so simple? Surely this should be the same as the above example:
pub fn get_flux_capacitor() {
let is_prod = (1 + 1) == 2;
...
}
But how far does that extend? The compiler's constant propagation is mostly an implementation detail. You don't want the types in your program to depend on how smart this version of the compiler is.
3. What did you actually mean?
As a human looking at this code, it looks like something is missing. Why are you branching on true at all? Why not just write FluxCapacitor::new()? Perhaps there's logic missing to check and see if a env=DEV environment variable is missing. Perhaps a trait object should actually be used so that you can take advantage of runtime polymorphism.
In this kind of situation where you're asking the computer to do something that doesn't seem quite right, Rust often chooses to throw its hands up and ask you to fix the code.
You're right, in this very specific case (where condition=true statically), the compiler could be made able to detect that the else branch is unreachable and therefore number must be 5.
This is just a contrived example, though... in the more general case, the value of condition would only be dynamically known at runtime.
It's in that case, as other have said, that inference becomes hard to implement.
On that topic, there are two things I haven't seen mentioned yet.
The Rust language design tends to err on the side of doing things as
explicitly as possible
Rust type inference is only local
On point #1, the explicit way for Rust to deal with the "this type can be one of multiple types" use case are enums.
You can define something like this:
#[derive(Debug)]
enum Whatsit {
Num(i32),
Text(&'static str),
}
and then do let number = if condition { Num(5) } else { Text("six") };
On point #2, let's see how the enum (while wordier) is the preferred approach in the language. In the example from the book we just try printing the value of number.
In a more real-case scenario we would at one point use number for something other than printing.
This means passing it to another function or including it in another type. Or (to even enable use of println!) implementing the Debug or Display traits on it. Local inference means that (if you can't name the type of number in Rust), you would not be able to do any of these things.
Suppose you want to create a function that does something with a number;
with the enum you would write:
fn do_something(number: Whatsit)
but without it...
fn do_something(number: /* what type is this? */)
In a nutshell, you're right that in principle it IS doable for the compiler to synthesize a type for number. For instance, the compiler might create an anonymous enum like Whatsit above when compiling that code.
But you - the programmer - would not know the name of that type, would not be able to refer to it, wouldn't even know what you can do with it (can I multiply two "numbers"?) and this would greatly limit its usefulness.
A similar approach was followed for instance to add closures to the language. The compiler would know what specific type a closure has, but you, the programmer, would not. If you're interested I can try finding out discussions on the difficulties that the approach introduced in the design of the language.
Learning Rust (yay!) and I'm trying to understand the intended idiomatic programming required for certain iterator patterns, while scoring top performance. Note: not Rust's Iterator trait, just a method I've written accepting a closure and applying it to some data I'm pulling off of disk / out of memory.
I was delighted to see that Rust (+LLVM?) took an iterator I had written for sparse matrix entries, and a closure for doing sparse matrix vector multiplication, written as
iterator.map_edges({ |x, y| dst[y] += src[x] });
and inlined the closure's body in the generated code. It went quite fast. :D
If I create two of these iterators, or use the first a second time (not a correctness issue) each instance slows down quite a lot (about 2x in this case), presumably because the optimizer no longer chooses to do specialization because of the multiple call sites, and you end up doing a function call for each element.
I'm trying to understand if there are idiomatic patterns that keep the pleasant experience above (I like it, at least) without sacrificing the performance. My options seem to be (none satisfying this constraint):
Accept dodgy performance (2x slower is not fatal, but no prizes either).
Ask the user to supply a batch-oriented closure, so acting on an iterator over a small batch of data. This exposes a bit much of the internals of the iterator (the data are compressed nicely, and the user needs to know how to unwrap them, or the iterator needs to stage an unwrapped batch in memory).
Make map_edges generic in a type implementing a hypothetical EdgeMapClosure trait, and ask the user to implement such a type for each closure they want to inline. Not tested, but I would guess this exposes distinct methods to LLVM, each of which get nicely inlined. Downside is that the user has to write their own closure (packing relevant state up, etc).
Horrible hacks, like make distinct methods map_edges0, map_edges1, ... . Or add a generic parameter the programmer can use to make the methods distinct, but which is otherwise ignored.
Non-solutions include "just use for pair in iterator.iter() { /* */ }"; this is prep work for a data/task-parallel platform, and I would like to be able to capture/move these closures to work threads rather than capturing the main thread's execution. Maybe the pattern I should be using is to write the above, put it in a lambda/closure, and ship it around instead?
In a perfect world, it would be great to have a pattern which causes each occurrence of map_edges in the source file to result in different specialized methods in the binary, without forcing the entire project to be optimized at some scary level. I'm coming out of an unpleasant relationship with managed languages and JITs where generics would be the only way (I know of) to get this to happen, but Rust and LLVM seem magical enough that I thought there might be a good way. How do Rust's iterators handle this to inline their closure bodies? Or don't they (they should!)?
It seems that the problem is resolved by Rust's new approach to closures outlined at
http://smallcultfollowing.com/babysteps/blog/2014/11/26/purging-proc/
In short, Option 3 above (make functions generic with respect to a new closure type) is now transparently implemented when you make an implementation generic using the new closure traits. Rust produces the type behind the scenes for you.
Closed. This question is off-topic. It is not currently accepting answers.
Want to improve this question? Update the question so it's on-topic for Stack Overflow.
Closed 10 years ago.
Improve this question
I do not have any argument opposing why we need only a single universal class. However why not we have two universal classes, say an Object and an AntiObject Class.
In nature and in science we find the concept of duality - like Energy & Dark Energy; Male & Female; Plus & Minus; Multiply & Divide; Electrons & Protons; Integration & Derivation; and in set theory. There are so many examples of dualism that it is a philosophy in itself. In programming itself we see Anti-Patterns which helps us to perform work in contrast to how we use Design patterns.
We call it object-oriented programming. Is that a limiting factor or is there something fundamental I am missing in understanding the formation of programming languages?
Edit:
I am not sure, but the usefulness of this duality concept may lie in creating garbage collectors that create AntiObjects that combine with free or loose Objects to destruct themselves, thereby releasing memory. Or may be AntiObjects work along with Objects to create a self-modifying programming language - that allows us to create a safe self modifying code, do evolutionary computing using genetic programming, do hiding of code to prevent reverse engineering.
I've moved this question to Computer Science Site of Stack Exchange, as this is considered off-topic here. Please use that if you want to comment/answer this question.
The inheritance tree is commonly (as it is in C#) a tree, with a single root, for a number of reasons, which all seem to lead back to one big one:
If there were multiple roots, there wouldn't be a way to specify "any type of object" (aside from something like C++'s void *, which would be hideous as it tosses away any notion of "type").
Even the idea of "any type of object" loses some usefulness, as you can no longer guarantee anything about the objects you'll be accepting. How do you say "all objects have properties a, b, and c" in such a way as to let programs actually use them? You'd need an interface that all of them implement...and then, that interface becomes the root type.
GC'able languages would be useless if they couldn't collect every type of object they manage. Oops, there goes that "any type of object" again!
All-around, it's simpler to have one type be the root of the hierarchy. It lets you make contracts/guarantees/etc that apply to every object in the system, and makes fewer demands on code that wants to be able to deal with objects in a universal manner.
C++ gets away with having multiple root types because (1) C++ allows multiple inheritance, so objects can bridge the gaps between inheritance trees; (2) it has templates (which are far, far more able than generics to take any type of object); (3) it can discard and sidestep any notion of "type" altogether via means like void *; and (4) it doesn't offer to manage and collect your objects for you.
C# didn't want all the complexity of multiple inheritance and templates, and it wanted garbage collection.
In Nature, if in a family there are two children who are totally different and opposite from each other, still they have common parents.
All those examples which you have given come under common category. For e.g. Male and Female comes under Homosapiens Category. Plus and Minus come under Operator category.
In OOPS also there are two types. Reference type and value type but still they both come under object.
What you are suggesting is also good. But let us for a second, in a universe, accept what you are suggesting. Still there will be a Super_Class containing your Object and AntiObject class. So it has to stop somewhere and in OOPS object is that class where it stops.