Rust: Difference between revisions
| Line 101: | Line 101: | ||
==Using trait's instead of the struct== |
==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. |
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 { |
pub struct ContextRaw { |
||
Revision as of 03:55, 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.
Consider some context that has a random number generator. I was wanting something similar for a Genetic programming experiment:
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.
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 allows for generics to be avoided.
When turning something into a Box on the struct, the struct's constructor must be updated, any mutator functions and the at the constructor call site. Users/consumers/owners of the struct however are unaffected.
This requires dynamic dispatch adding overhead.
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.
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
For example rusts simple log class. 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 suck.