Rust: Difference between revisions
| Line 41: | Line 41: | ||
But it requires dynamic dispatch adding overhead. |
But it requires dynamic dispatch adding overhead. |
||
When |
When Boxing a member variable in a struct, the struct's constructor must be updated to take a box and any setter methods. Also the at the constructor call site must now Box it's value. Users/consumers/owners of the struct however are unaffected. |
||
extern crate rand; |
extern crate rand; |
||
Revision as of 04:11, 10 December 2016
Trait Objects
Swapping between different ownership/reference types in Rust is a huge pain point. The generics can ripple throughout a program making maintenance/refactoring a big hassle. It's a barrier to how programs normally evolve over time.
Consider some context that has a random number generator. I was wanting something similar for a Genetic programming experiment:
Another example would be passing in a Reader/Writer/Logger to some App context.
Without Traits
This suffers from fixing the Rng to a specific implementation and prevents dependency inversion causing problems with things like testing and forcing any downstream users to be stuck with the choice. No one will have the option of using their quantum hardware random number generator now.
extern crate rand; use rand::StdRng;
pub struct Context {
rng: StdRng,
}
impl Context {
pub fn new() -> Context {
let seed: &[_] = &[1, 2, 3, 4];
let mut rng = StdRng::from_seed(seed);
}
pub fn set_rng(&self, rng: StdRng) {
self.rng = rng;
}
}
impl SomeTrait for Context {
//...Blah
fn some_function(self) {}
}
fn use_context(ctx: &Context) {
//...Blah
}
fn main() {
let mut ctx = Context::new();
use_context(&ctx)
}
Using Box's
Using boxes is simple and allows for generics to be avoided.
But it requires dynamic dispatch adding overhead.
When Boxing a member variable in a struct, the struct's constructor must be updated to take a box and any setter methods. Also the at the constructor call site must now Box it's value. Users/consumers/owners of the struct however are unaffected.
extern crate rand;
use rand::{Rng, StdRng};
pub struct Context {
rng: Box<Rng>, // <- This is changed
}
impl Context {
pub fn new(rng: Box<Rng>) -> Context { // <- Now this line must be changed
Context {rng: rng}
}
pub fn set_rng(&self, rng: Box<Rng>) { // <- ...this line must be changed
self.rng = rng;
}
}
impl SomeTrait for Context {
//...Blah
}
fn use_context(ctx: &Context) {
//...
}
fn main() {
let seed: &[_] = &[1, 2, 3, 4];
let mut rng = StdRng::from_seed(seed);
let mut ctx = Context::new(Box::new(rng)); // <- ...this line must be changed
use_context(&ctx)
}
Using &'s
Requires lifetime to be specified in generics. This ripples throughout the program. Consider the extra complexity of stitch from a box to a & and what happens if you have multiple different member variable references, maybe mixing both boxes and borrows...
extern crate rand;
use rand::{Rng, StdRng};
pub struct Context<'a> { // <- This must be changed
rng: &'a Rng, // <- So this can be changed
}
impl Context<'a> { // The same generic signature must be added here
pub fn new(rng: &Rng) -> Context<'a> { // <- ...Here
Context {rng: rng}
}
pub fn set_rng(&self, rng: &Rng) {
self.rng = rng;
}
}
impl SomeTrait for Context<'a> { // <- ...Here
//...Blah
}
fn use_context(ctx: &Context<'a>) { // <- ...and here
//...
}
fn main() {
let seed: &[_] = &[1, 2, 3, 4];
let mut rng = StdRng::from_seed(seed);
let mut ctx = Context::new(&rng);
use_context(&ctx)
}
Using trait's instead of the struct
Requires traits to be defined. Still needs generics on the structs functions such as the constructor. However user/consumers functions are uneffected. But any structs that want to hold a reference this struct must now deal with the same trait object problem.
pub struct ContextRaw {
}
Using a global
Doesn't allow for multiple different contexts with their own separate Rng generators. Maybe some thread_local variant would work but that seems to be a pain and a hack. Globals must be initialised in a thread safe way. Testing and changing defaults is a bit of extra work but can be done via monkey patching. Globals suck.
For example rusts simple log class or the stdout() global.
Using closures
Pass in the function of the object you want to call, rather than the object. More work at the construct site and seems to defeat the point of having traits. Not good if what your passing in has lots of functions that are used.