I sometimes find myself writing abstract classes with partial implementation in C#:
abstract public class Executor {
abstract protected bool Before();
abstract protected bool During();
abstract protected bool After();
protected bool Execute() {
var success = false;
if (Before()) {
if (During()) {
if (After()) {
success = true;
}
}
}
return success;
}
}
Notwithstanding the wisdom of such a control structure, how would I accomplish this (partial shared implementation) in a functional language like rust?
Rust is an imperative language, but it follows many functional programming paradigms. In computer science, functional programming is a programming paradigm where programs are constructed by applying and composing functions.
Under this definition, then, Rust is object-oriented: structs and enums have data and impl blocks provide methods on structs and enums. Even though structs and enums with methods aren't called objects, they provide the same functionality, under the Gang of Four's definition of objects.
In rust we do not create objects itself, we call them instances. To create a instance you just use the struct name and a pair of {}, inside you put the name of the fields with values.
In Rust, there is no concept of "inheriting" the properties of a struct. Instead, when you are designing the relationship between objects do it in a way that one's functionality is defined by an interface (a trait in Rust).
I'm not within reach of a rust compiler, so forgive broken code.
On a functional side of things, you could make a struct that holds three functions and invoke them
struct Execution {
before: @fn() -> bool,
during: @fn() -> bool,
after: @fn() -> bool
}
fn execute (e: Execution) -> bool {
...
}
but once you have a function as a first class value, you could pass say, a list of boolean functions to check against instead of fixed three, or something else depending on what are you trying to achieve.
On a rust side of things, you can make it more "object oriented" by using traits
trait Executable {
fn execute(&self);
}
impl Execution {
fn execute(&self) {
...
}
}
Using default methods on traits is one way (and will probably/hopefully be the idiomatic way in the future; until recently, the struct
-with-closures method @Slartibartfast demonstrates was the only thing that actually worked):
#[allow(default_methods)];
trait Executable {
fn before(&self) -> bool;
fn during(&self) -> bool;
fn after(&self) -> bool;
fn execute(&self) -> bool {
self.before() && self.during() && self.after()
}
}
impl Executable for int {
fn before(&self) -> bool { *self < 10 }
fn during(&self) -> bool { *self < 5 }
fn after(&self) -> bool { *self < 0 }
// execute is automatically supplied, if it is not implemented here
}
Note that it is possible for an implementation of Executable
to override execute
at the moment (I've opened an issue about a #[no_override]
attribute that would disable this).
Also, default methods are experimental and prone to crashing the compiler (yes, more so than the rest of Rust), but they are improving quickly.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With