Rust dependency inversion with nested structs using RC - struct

I am trying to do some dependecy inversion in Rust. My goal is to create a struct that accepts any other struct instance that complies with a trait.
This wont compile, but is basically what I would like to achieve:
// Trait for any kind of tool I may use in my work
trait ToolTrait {
fn do_work(&self);
}
struct Saw {
size: i32,
}
impl ToolTrait for Saw {
fn do_work(&self) {
println!("I'm a saw, size: {}cm", self.size);
}
}
struct ScrewDriver {
size: i32,
}
impl ToolTrait for ScrewDriver {
fn do_work(&self) {
println!("I'm a screwdriver, size: {}mm", self.size);
}
}
// Work uses any struct that complies with ToolTrait
pub struct Work {
tool: ToolTrait,
}
impl Work {
// We can instantiate Work and set tool to it
fn new(tool: ToolTrait) -> Work {
Work { tool }
}
}
let saw = Saw { size: 30 };
let work_1 = Work::new(saw);
work_1.tool.do_work(); // "I'm a saw, size: 30cm"
let screwdriver = ScrewDriver { size: 4 };
let work_2 = Work::new(screwdriver);
work_2.tool.do_work(); // "I'm a screwdriver, size: 4mm"
Now, regarding Rust compiler, we have several error warnings:
pub struct Work {
tool: ToolTrait,
}
// trait objects without an explicit `dyn` are deprecated
Ok, lets add dyn both in Work and impl Work:
pub struct Work {
tool: dyn ToolTrait,
}
impl Work {
fn new(tool: dyn ToolTrait) -> Work {
Work {
tool: Rc::new(tool),
}
}
}
Perfect, no error in Work. But focusing on impl Work we have this error:
impl Work {
fn new(tool: ToolTrait) -> Work {
Work {
tool: Rc::new(tool),
}
}
}
// the size for values of type `(dyn main::ToolTrait + 'static)` cannot be known at compilation time
Makes sense: Work can not know what size tool will have. But how can I fix it?
I wrapped dyn ToolTrait with std::rc::Rc as Rc<dyn ToolTrait>:
pub struct Work {
tool: Rc<dyn ToolTrait>,
}
impl Work {
// We can instantiate Work and set tool to it
fn new(tool: Rc<dyn ToolTrait>) -> Work {
Work { tool }
}
}
This works, but is this the correct way to achieve dependency inversion as we usually do in object oriented programming?

You can make your struct generic over the tool:
// Trait for any kind of tool I may use in my work
trait ToolTrait {
fn do_work(&self);
}
struct Saw {
size: i32,
}
impl ToolTrait for Saw {
fn do_work(&self) {
println!("I'm a saw, size: {}cm", self.size);
}
}
struct ScrewDriver {
size: i32,
}
impl ToolTrait for ScrewDriver {
fn do_work(&self) {
println!("I'm a screwdriver, size: {}mm", self.size);
}
}
// Work uses any struct that complies with ToolTrait
pub struct Work<T> {
tool: T,
}
impl<T: ToolTrait> Work<T> {
// We can instantiate Work and set tool to it
fn new(tool: T) -> Work<T> {
Work { tool }
}
}
fn main() {
let saw = Saw { size: 30 };
let work_1 = Work::new(saw);
work_1.tool.do_work(); // "I'm a saw, size: 30cm"
let screwdriver = ScrewDriver { size: 4 };
let work_2 = Work::new(screwdriver);
work_2.tool.do_work(); // "I'm a screwdriver, size: 4mm"
}

Related

Allowing extension (beyond the crate) of implementation with event loop

Within the crate we can happily do something like this:
mod boundary {
pub struct EventLoop;
impl EventLoop {
pub fn run(&self) {
for _ in 0..2 {
self.handle("bundled");
self.foo();
}
}
pub fn handle(&self, message: &str) {
println!("{} handling", message)
}
}
pub trait EventLoopExtend {
fn foo(&self);
}
}
use boundary::EventLoopExtend;
impl EventLoopExtend for boundary::EventLoop {
fn foo(&self) {
self.handle("extended")
}
}
fn main() {
let el = boundary::EventLoop{};
el.run();
}
But if mod boundary were a crate boundary we get error[E0117]: only traits defined in the current crate can be implemented for arbitrary types.
I gather that a potential solution to this could be the New Type idiom, so something like this:
mod boundary {
pub struct EventLoop;
impl EventLoop {
pub fn run(&self) {
for _ in 0..2 {
self.handle("bundled");
self.foo();
}
}
pub fn handle(&self, message: &str) {
println!("{} handling", message)
}
}
pub trait EventLoopExtend {
fn foo(&self);
}
impl EventLoopExtend for EventLoop {
fn foo(&self) {
self.handle("unimplemented")
}
}
}
use boundary::{EventLoop, EventLoopExtend};
struct EventLoopNewType(EventLoop);
impl EventLoopExtend for EventLoopNewType {
fn foo(&self) {
self.0.handle("extended")
}
}
fn main() {
let el = EventLoopNewType(EventLoop {});
el.0.run();
}
But then the problem here is that the extended trait behaviour isn't accessible from the underlying EventLoop instance.
I'm still quite new to Rust, so I'm sure I'm missing something obvious, I wouldn't be surprised if I need to take a completely different approach.
Specifically in my case, the event loop is actually from wgpu, and I'm curious if it's possible to build a library where end users can provide their own "render pass" stage.
Thanks to #AlexN's comment I dug deeper into the Strategy Pattern and found a solution:
mod boundary {
pub struct EventLoop<'a, T: EventLoopExtend> {
extension: &'a T
}
impl<'a, T: EventLoopExtend> EventLoop<'a, T> {
pub fn new(extension: &'a T) -> Self {
Self { extension }
}
pub fn run(&self) {
for _ in 0..2 {
self.handle("bundled");
self.extension.foo(self);
}
}
pub fn handle(&self, message: &str) {
println!("{} handling", message)
}
}
pub trait EventLoopExtend {
fn foo<T: EventLoopExtend>(&self, el: &EventLoop<T>) {
el.handle("unimplemented")
}
}
}
use boundary::{EventLoop, EventLoopExtend};
struct EventLoopExtension;
impl EventLoopExtend for EventLoopExtension {
fn foo<T: EventLoopExtend>(&self, el: &EventLoop<T>) {
el.handle("extended")
}
}
fn main() {
let el = EventLoop::new(&EventLoopExtension {});
el.run();
}
The basic idea is to use generics with a trait bound. I think the first time I looked into this approach I was worried about type recursion. But it turns out passing the EventLoop object as an argument to EventLoopExtend trait methods is perfectly reasonable.

How do i resolve type annotations needed cannot infer type for type parameter `T` ? What type annotation is needed to compile this code?

The blockchain struct definition, It defines a type and i use the type
pub struct Blockchain<T = SledDb> {
pub storage: T,
pub chain: Vec<Block>,
pub tip: Arc<RwLock<String>>,
pub height: AtomicUsize,
pub mempool: Mempool,
pub wallet: Wallet,
pub accounts: Account,
pub stakes: Stake,
pub validators: Validator,
}
This code is checking if stake is valid.The code for mining a block, the error is immited by is_staking_valid function. I don't know what type its asking for since i already specified one.
impl<T: Storage> Blockchain<T> {
pub fn is_staking_valid(
balance: u64,
difficulty: u32,
timestamp: i64,
prev_hash: &String,
address: &String,
) -> bool {
let base = BigUint::new(vec![2]);
let balance_diff_mul = base.pow(256) * balance as u32;
let balance_diff = balance_diff_mul / difficulty as u64;
let data_str = format!("{}{}{}", prev_hash, address, timestamp.to_string());
let sha256_hash = digest(data_str);
let staking_hash = BigUint::parse_bytes(&sha256_hash.as_bytes(), 16).expect("msg");
staking_hash <= balance_diff
}
pub fn mine_block(&mut self, data: &str) -> Option<Block> {
if self.mempool.transactions.len() < 2 {
info!("Skipping mining because no transaction in mempool");
return None;
}
let balance = self
.stakes
.get_balance(&self.wallet.get_public_key())
.clone();
let difficulty = self.get_difficulty();
info!("New block mining initialized with difficulty {}", difficulty);
let timestamp = Utc::now().timestamp();
let prev_hash = self.chain.last().unwrap().hash.clone();
let address = self.wallet.get_public_key();
if Blockchain::is_staking_valid(balance, difficulty, timestamp, &prev_hash, &address){
let block = self.create_block(&data, timestamp);
self.storage.update_blocks(&prev_hash, &block, self.height.load(Ordering::Relaxed));
Some(block)
} else {
None
}
}
}
Please find the compiler error below
error[E0282]: type annotations needed
--> src/blocks/chain.rs:173:12
|
173 | if Blockchain::is_staking_valid(balance, difficulty, timestamp, &prev_hash, &address){
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot infer type for type parameter `T`
For more information about this error, try `rustc --explain E0282`.
Minimized example:
pub struct Blockchain<T> {
pub storage: T,
}
impl<T> Blockchain<T> {
pub fn is_staking_valid() {
todo!()
}
pub fn mine_block(&mut self) {
Blockchain::is_staking_valid();
}
}
Playground
The reason for this error is that Blockchain::<T1>::is_staking_valid and Blockchain::<T2>::is_staking_valid are, as well as compiler is concerned, two separate, entirely unrelated functions. Yes, they have the same code, and yes, they will be deduplicated by the optimizer, but this doesn't have to be the case - e.g., if this function used some associated item available on T:
trait Stakable {
const IS_VALID: bool;
}
impl Stakable for () {
const IS_VALID: bool = false;
}
impl Stakable for i32 {
const IS_VALID: bool = true;
}
struct Blockchain<T> {
pub _storage: T,
}
impl<T: Stakable> Blockchain<T> {
fn validate() {
if !T::IS_VALID {
panic!("Type is not valid");
}
}
}
fn main() {
// This panics - we catch this panic and show that it has indeed happened
std::panic::catch_unwind(|| Blockchain::<()>::validate()).unwrap_err();
// This executes successfully
Blockchain::<i32>::validate();
}
Playground
Because of the possible ambiguity, compiler refuses to choose by itself and forces you to make the selection explicitly.
So, you have several possible ways to go:
Make is_staking_valid a free function, instead of associated function of Blockchain. In this case, it won't be able to depend on Blockchain's type parameter, therefore the call will be unambiguous.
Call Self::is_staking_valid instead of Blockchain::is_staking_valid. In this case, Self will be replaced with Blockchain::<T>, with T taken from the currently executed method; this will, again, resolve ambiguity.
Make is_staking_valid a method on Blockchain, i.e. make it receive &self, and call it via self.is_staking_valid().
Not recommended, but still possible, - make is_staking_valid an associated function on Blockchain<T> for some specific T, e.g.:
pub struct Blockchain<T> {
pub storage: T,
}
impl Blockchain<()> {
// Note - no free type parameters here!
pub fn is_staking_valid() {
todo!()
}
}
impl<T> Blockchain<T> {
pub fn mine_block(&mut self) {
// Here, `Blockchain` is `Blockchain::<()>` - the method is set
Blockchain::is_staking_valid();
}
}

Extending On An SO Answered Question About Strategy Pattern Implementation

As answered by the ever so helpful and ubiquitous Shepmaster, could someone help me with a syntax hurdle I'm encountering?
In the previous answer, Strategy::execute() and Context::do_things() returns ().
How does one implement if a generic type is returned? Or am I missing some fundamental perspective in Rust?
I tried the following code but am currently stuck at:
struct Context<S> {
strategy: S,
}
impl<S> Context<S>
where
S: Strategy,
{
fn do_things(&self) -> T {
println!("Common preamble");
self.strategy.execute()
}
}
trait Strategy<T> {
fn execute(&self) -> T;
}
struct ConcreteStrategyA;
impl Strategy<AStruct> for ConcreteStrategyA {
fn execute(&self) -> AStruct {
println!("ConcreteStrategyA");
AStruct::default()
}
}
struct AStruct {
id: u32,
}
impl Default for AStruct {
...
}
struct ConcreteStrategyB;
impl Strategy<BStruct> for ConcreteStrategyB {
fn execute(&self) -> BStruct {
println!("ConcreteStrategyB");
BStruct::default()
}
}
struct BStruct {
id: u32,
}
impl Default for BStruct {
...
}
I have no idea where to put T for Context::do_things() -> T.
I looked around but some other samples return () as well.
Online tinkering
Thanks for reading.
Depending on what you're trying to do, it might be better to use an associated type instead of a generic. When choosing between associated types and generic parameters, you should use a generic if the caller can choose the type to use. You should use an associated type if the implementation determines the type.
Although there are exceptions (e.g. Into::into), most of the time if a type appears only in the return of a method, then this is a good indication that it should probably be an associated type. OTOH if a type is used for a parameter, then there is a fair chance that it should be a generic.
In your case, using an associated type would look like this:
struct Context<S> {
strategy: S,
}
impl<S> Context<S>
where
S: Strategy,
{
fn do_things(&self) -> S::Output {
println!("Common preamble");
self.strategy.execute()
}
}
trait Strategy {
type Output;
fn execute(&self) -> Self::Output;
}
struct ConcreteStrategyA;
impl Strategy for ConcreteStrategyA {
type Output = AStruct;
fn execute(&self) -> AStruct {
println!("ConcreteStrategyA");
AStruct::default()
}
}
#[derive (Default)]
struct AStruct {
id: u32,
}
struct ConcreteStrategyB;
impl Strategy for ConcreteStrategyB {
type Output = BStruct;
fn execute(&self) -> BStruct {
println!("ConcreteStrategyB");
BStruct::default()
}
}
#[derive (Default)]
struct BStruct {
id: u32,
}
Playground
I think using Strategy<T> more often is the key:
impl<S, T> Context<S, T>
where
S: Strategy<T>,
{
fn do_things(&self) -> T {
println!("Common preamble");
self.strategy.execute()
}
}
See here: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=a3a14895e22ca57f5f96c855b7046257

How do I 'force' structs to implement the same traits?

I have the following:
pub struct OpBStruct {
title: String,
output_vale: i32,
}
impl OpBStruct {
pub fn new_OpB(in_title: String, in_output_vale: i32) -> OpBStruct {
OpBStruct {
title: in_title,
output_vale: in_output_vale,
}
}
}
pub struct OpCStruct {
title: String,
another_value: String,
output_vale: i32,
}
impl OpCStruct {
pub fn new_OpC(in_title: String, in_another_value: String, in_output_vale: i32) -> OpCStruct {
OpCStruct {
title: in_title,
another_value: in_another_value,
output_vale: in_output_vale,
}
}
}
impl A {
pub fn new_A(in_name: String, in_operator: Op) -> A {
A {
name: in_name,
operator: in_operator,
}
}
}
pub enum Op {
OpB(OpBStruct),
OpC(OpCStruct),
}
pub struct A {
name: String,
operator: Op,
}
impl A {
pub fn new_A(in_name: String, in_operator: Op) -> A {
A {
name: in_name,
operator: in_operator,
}
}
}
The exact structure of OpBStruct and OpCStruct are arbitrary and could be anything.
How do I make sure OpBStruct and OpCStruct implement a certain trait?
trait OpTrait {
pub fn get_op_output(&self) -> i32;
}
I thought about making a sort of constructor function that checked for an OpTrait trait requirement and it would be the only way one could create an Op instance, but each operator requires different initialization parameters and there's no way to specify a variable number of inputs for a function in Rust.
Something like this doesn't work because there's no way to input the initialization parameters:
pub fn new_op<T: OpTrait>(operator: T) {
// --snip--
}
I thought about somehow using the new_A method implemented on A to check if the in_operator has implemented the trait, but I'm not sure how to do that either.
What is the correct pattern for this? If there is none, I can just implement the trait for each Op with no sort of interface around it.
I would also recommend writing a test, however you can write a function which is generic over a type but takes no arguments:
struct X {}
trait Y {
fn yo();
}
fn is_y<T: Y>(){}
Then you can add the following line to do the check
is_y::<X>();
which will compile only if X implements Y.
Using a unit test would be a fairly straightforward way to enforce that you want a given trait on a struct. You can do it via implicit test code, but a small utility function to do so can express the intent a little more clearly.
If you have indicated trait inputs on functions in the rest of the code, it might come out fairly naturally without the unit test. The test has the advantage of letting you have an opportunity to explicitly check some invariant of the trait implementation too.
struct A {
val: u8,
}
struct B {
val: u32,
}
trait ExpandToU64 {
fn to_u64(&self) -> u64;
}
impl ExpandToU64 for A {
fn to_u64(&self) -> u64
{
self.val as u64
}
}
fn trait_tester<E>(a: E)
where E: ExpandToU64
{
// the utility function doesn't have to even use the trait...
// but you probably want to exercise the logic a bit
//let v = a.to_u64();
let v = 24u64;
println!("{:?}", v);
}
#[test]
fn test_needs_trait_ExpandToU64() {
let a = A { val:1 };
trait_tester(a);
let b = B { val:2 };
trait_tester(b);
// This fails with a compile error
// "the trait `ExpandToU64` is not implemented for `B`"
}

Is it possible to create a macro to implement builder pattern methods?

I have a builder pattern implemented for my struct:
pub struct Struct {
pub grand_finals_modifier: bool,
}
impl Struct {
pub fn new() -> Struct {
Struct {
grand_finals_modifier: false,
}
}
pub fn grand_finals_modifier<'a>(&'a mut self, name: bool) -> &'a mut Struct {
self.grand_finals_modifier = grand_finals_modifier;
self
}
}
Is it possible in Rust to make a macro for methods like this to generalize and avoid a lot of duplicating code? Something that we can use as the following:
impl Struct {
builder_field!(hello, bool);
}
After reading the documentation, I've come up with this code:
macro_rules! builder_field {
($field:ident, $field_type:ty) => {
pub fn $field<'a>(&'a mut self,
$field: $field_type) -> &'a mut Self {
self.$field = $field;
self
}
};
}
struct Struct {
pub hello: bool,
}
impl Struct {
builder_field!(hello, bool);
}
fn main() {
let mut s = Struct {
hello: false,
};
s.hello(true);
println!("Struct hello is: {}", s.hello);
}
It does exactly what I need: creates a public builder method with specified name, specified member and type.
To complement the already accepted answer, since it is 4 years old by now, you should check out the crate rust-derive-builder. It uses procedural macros to automatically implement the builder pattern for any struct.

Resources