A pattern I use frequently when writing Python is using class-only designs. I'll create an abstract base class, consisting of only classmethod and staticmethod methods. From this base, I can derive subclasses with specialized behavior for these methods. Since each of these subclasses have the same interface, and don't have any instance methods, I can pass around the classes themselves to client code, as opposed to instances of those classes.
As a simple, yet real/relevant example, here's how I would tackle needing to support multiple file formats for config data:
import abc
import yaml
import json
class ConfigReader(abc.ABC):
#classmethod
def from_file_path(cls, path: str):
with open(path) as fo:
return cls.from_string(fo.read())
#classmethod
#abc.abstractmethod
def from_string(cls, s: str):
pass
class YamlConfigReader(ConfigReader):
#classmethod
def from_string(cls, s: str):
return yaml.load(s)
class JsonConfigReader(ConfigReader):
#classmethod
def from_string(cls, s: str):
return json.loads(s)
Client code can then use the classes themselves in lieu of instances of those classes to do config parsing:
import typing as tp
def read_config_file(path: str, config_reader: tp.Type[ConfigReader]):
return config_reader.from_file_path(path)
print(read_config_file('config.yaml', YamlConfigReader))
print(read_config_file('config.json', JsonConfigReader))
I'm trying to do something similar to the above in Rust, but I seem to be running into issues. My initial approach used traits and associated methods:
use std::path::Path;
use std::fs::File;
use std::io::Read;
use std::io::Error;
pub trait ConfigReader {
fn from_str<S: AsRef<str>>(s: S) -> Result<String, Error>;
fn from_file<P: AsRef<Path>>(p: P) -> Result<String, Error> {
let p = p.as_ref();
let mut f = File::open(p)?;
let mut buffer = String::new();
f.read_to_string(&mut buffer)?;
Self::from_str(buffer)
}
}
pub struct YamlConfigReader;
pub struct JsonConfigReader;
impl ConfigReader for YamlConfigReader {
fn from_str<S: AsRef<str>>(_s: S) -> Result<String, Error> {
Ok("dummy".to_string())
}
}
impl ConfigReader for JsonConfigReader {
fn from_str<S: AsRef<str>>(_s: S) -> Result<String, Error> {
Ok("dummy".to_string())
}
}
fn read_config_file<P: AsRef<Path>>(p: P, config_reader: &ConfigReader) -> Result<String, Error> {
config_reader.from_file(p)
}
fn main() {}
(playground)
This gives me the error:
error[E0038]: the trait `ConfigReader` cannot be made into an object
--> src/main.rs:37:1
|
37 | fn read_config_file<P: AsRef<Path>>(p: P, config_reader: &ConfigReader) -> Result<String, Error> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `ConfigReader` cannot be made into an object
|
= note: method `from_str` has no receiver
= note: method `from_file` has no receiver
Is this sort of pattern possible in Rust? If not, what are some ways I can emulate this sort of behavior of decoupled, modular, stateless interfaces?
In Rust, you would treat a type parameter as, well, a type parameter:
fn read_config_file<P: AsRef<Path>, C: ConfigReader>(p: P) -> Result<String, Error> {
C::from_file(p)
}
This limits the dynamisms of such constructs.
Related
Let's say that I create a protocol like this:
protocol A {
associatedtype T
func computeSomething(with:T) -> Double
}
In my generic typed class, I would like to do something like this:
class B<U> {
var doSomething:A<U>
}
This thing is that this generates an error, but I would like to accept any type that would support computeSomething on my type U but I don't know at all how to do that?
Edit for clarity
Basically if A was a generic struct or class, that would be possible, but what if no default implementation (provided by class or struct) makes sense here and the only thing I want is to ensure that the type does what I want?
Edit #2 (with concrete example)
I wanted to simplify my question which makes it pretty hard to understand so here is a still simplified and fictional problem that probably matches better the issue I am encountering:
I am writing a generic class that processes its generic type T:
class Process<T> { ... }
The class Process itself includes code that processes T, but in order for this code to work, it needs T to conform to some protocols, for instance:
protocol A {
func mixWith(other:A) -> A
}
protocol B {
var isFoo:Bool { get set }
}
So my first approach was to simply require T to conform to those protocols:
class Process<T:<A,B>> { ... }
This looks like the simplest approach and probably is in many cases, but in my case I think that this actually is problematic, for this reason:
First, I may need to process the same type in many different ways, and changing a way a type is being processed often requires changing the actual implementation of protocols A and B for instance in this case, fooProcess and barProcess are both of type Process with generic type MyType:
let fooProcess = Process<MyType>()
let barProcess = Process<MyType>()
But I want fooProcess and barProcess to do different operations which in many cases would require to change the implementation of the A and B protocols of my MyType type and that's simply not possible.
So my first idea was to simply require some closures and variables to be defined so that I wouldn't need protocols anymore and would define the way data is being processed only in my Process class, a little bit like this:
class Process<T> {
//
var mix:(_ lhs:T, _ rhs:T)->(T)
var isFoo:(_ a:T)->(Bool)
...
}
There all of the processing would be directly implemented in my Processing class, again this would have looked like the right solution but now comes another issue, which led me to my associated type approach: it turns out that in many cases, the user of my Process class would want to get some default behaviour implemented by my framework, for instance, I could automatically implement protocol A and B for them as long as their class conformed to protocol X, here is how it did it:
protocol X:A,B {
...
}
extension protocol X {
// Here was my default implementation of A and B, which enabled my user to directly get A and B implemented as long as their type conformed to X
}
By using this method, I would let my user directly choose what they wanted to implement themselves, by conforming to protocol X they would only need to write a little bit of code and let my framework to all of the rest by itself, and if they wanted to implement themselves A or B they still could.
So if I am right, there is no way to do such a thing with my closures implementation.
So for this reason, I thought that an associated type protocol would be a good solution because here I could let my users easily get some default behaviour or write their own, so now we are getting back to my original question:
protocol AProcessing {
associatedtype U
func mix(_ lhs:U, _ rhs:U) -> U
}
protocol BProcessing {
associatedtype U
func isFoo(_ a:U) -> Bool
}
And then do something like that:
class Process<T> {
var aProcessor:AProcessing<T>
var bProcessor:BProcessing<T>
}
Here the advantage compared to closures is that I could write a special class conforming to AProcessing that could provide default implementation, this way:
class AutomaticAProcessing<T:X>:AProcessing { ... }
That would have enabled my users to so something like that:
var processData = Process<SomeType>()
processData.aProcessor = AutomaticAProcessing<SomeType>()
processData.bProcessor = TheirOwnImplemtation
Not only is this not possible in Swift, but it also feels like I am using too many "hacks" to get things done and there should be an easier language feature to do that, unfortunately I don't know what I should use.
I don't think it is possible, because the generic type of the protocol is specified in the class that implements it.
You could write something like this:
class B<U, P: A> where P.T == U {
var someVar: P?
}
But then you would need to specify a second parameter with the specific class. For example:
class C: A {
typealias T = String
func computeSomething(with: String) -> Double {
return 0.0
}
}
let b = B<String, C>()
let c = b.someVar
But it can't return a protocol with specific type in its associatedtype
One way would be to start with an empty generic struct, and then extend it on types where it makes sense:
struct A<T> {}
extension A where T: Base {
func computeSomething(with: T) -> Double {
return 1
}
}
Usage:
protocol Base {}
class B<U: Base> {
let doSomething = A<U>()
func foo(x: U) -> Double {
return doSomething.computeSomething(with: x)
}
}
class Z : Base {}
let x = B<Z>()
let y = x.foo(x: Z())
print(y)
To EDIT #2:
Remove associated types, and it should be workable:
protocol A {}
protocol B {}
protocol AProcessing {
func mix(_ lhs: A, _ rhs: A) -> A
}
protocol BProcessing {
func isFoo(_ a: B) -> Bool
}
Then, your processor:
class Process<T: A & B> {
var aProcessor: AProcessing!
var bProcessor: BProcessing!
func foo(_ a: T) -> Bool {
let x = aProcessor.mix(a, a)
guard let b = x as? B else { return false }
return bProcessor.isFoo(b)
}
}
And usage:
struct AutomaticAProcessing : AProcessing {
func mix(_ lhs: A, _ rhs: A) -> A { return lhs }
}
struct TheirOwnImplemtation : BProcessing {
func isFoo(_ a: B) -> Bool { return false }
}
struct SomeType : A, B {}
var processData = Process<SomeType>()
processData.aProcessor = AutomaticAProcessing()
processData.bProcessor = TheirOwnImplemtation()
let x = SomeType()
let y = processData.foo(x)
print(y)
When using ty in a macro, this works in nearly all cases I've tried.
However, it seems it cant be used to declare a new struct instance.
eg: $my_type { some_member: some_value }
A more comprehensive example
macro_rules! generic_impl {
($my_type:ty) => {
impl $rect_ty {
pub fn flip(&self) -> $my_type { self.some_method() }
pub fn swap(&self, &other: $my_type) -> { self.some_swap_method(other) }
// so far so good!
// now our troubles start :(
pub fn create(&self) -> $my_type {
return $my_type { foo: 1, bar: 2, baz: 2 };
// ^^^^^^^^ this fails!!!
}
}
}
}
// example use
generic_impl(MyStruct);
generic_impl(MyOtherStruct);
The error is:
error: expected expression, found `MyStruct`
Changing the ty to an expr means I can't use impl $my_type.
Besides passing in 2x arguments, one a ty the other an expr:
Is there a way to construct a struct based on a ty argument to a macro?
No, not with ty.
The simple fix is to capture an ident instead, which is valid in both contexts. If you need something more complex than a simple identifier, then you're probably going to need to capture the name (for construction) and the type (for other uses) separately and specify them both on use.
I've got code that looks like this:
class Base {
func launch(code1: Int, code2: Int) -> Bool { return false }
}
class A: Base {}
class B: Base {}
class C: Base {}
func trynext(obj: Base) -> Base? {
switch obj {
case is A: return B()
case is B: return C()
default: return nil
}
}
Basically, I've got a lot (like 20) of subclasses of a common base class and I need to go through them one by one. These subclasses represent parsers and I'm trying them one after another to discover which parser correctly parses some data.
If I fail on a parse, I call a function trynext to return the "next parser" to try. You can imagine that this switch statement can get unyieldly if the constructors take arguments (all subclasses take the same arguments), and the more subclasses there are, etc.
Is there any way I can streamline this code by putting the classes into some sort of array and loop through it somehow? The idea is to reduce the boilerplate so that I end up using a structure like [A, B, C] that implies the subclasses to try and the order to try them in.
I would use a Protocol (say: "Parser") to define an interface that describes what the parser can do. E.g.: parse(data);
Different Implementations of this Protocol (A, B, C…) will have their own code to deal with the parsing.
The Parsing Controller (or Manager or whatever name you come up with), will store an Array of Parser objects.
In a forEach loop, you can call each try Parser.parse(data); … and deal with either going to the next or aborting if the parsing was ok.
The particular implementations of your Parsers (A, B, C…) are irrelevant to the caller (as it should be). The ParsingController (where you have your switch right now), couldn't care less about what happens. It's only interested in success or failure to stop or try the next one respectively (if there's a next).
UPDATE: I have created a small Playground code you can paste and see what I mean.
UPDATE2: I have added an extension protocol so you can see how to so something akin to an abstract class to have a base/common set of functions/values.
import Swift
protocol Parser {
func parse(code1: Int, code2: Int) -> Bool
}
extension Parser {
var someCalculatedProperty: Int {
return 12345
}
func someCommonMethod() {
print("Some Common Method")
}
}
class A : Parser {
func parse(code1: Int, code2: Int) -> Bool { return false }
}
class B : Parser {
func parse(code1: Int, code2: Int) -> Bool { return false }
}
class C : Parser {
func parse(code1: Int, code2: Int) -> Bool { return true }
}
// Create 4 Parsers (two of the same type, just to demonstrate)
var parsers = [Parser](arrayLiteral: A(), A(), B(), C())
// Iterate the parsers until we get a success
for parser in parsers {
if parser.parse(0, code2: 1) {
print("Success")
// Just for fun, call common parser methods.
parser.someCommonMethod()
print(parser.someCalculatedProperty)
break
} else {
print("Fail")
}
}
Output should be:
Fail
Fail
Fail
Success
Some Common Method
12345
3 Failures (because A, A, B return false) and the the Success because of C that returns true. Then the common methods' output.
Trying to export a macro from module. Macro generates structure implementing some traits defined in module. Is there a way to get macro without importing that traits manually?
// src/lib.rs
#![crate_name="macro_test"]
#![crate_type="lib"]
#![crate_type="rlib"]
pub trait B<T> where T: Copy {
fn new(x: T) -> Self;
}
#[macro_export]
macro_rules! test {
( $n:ident ) => {
struct $n<T> where T: Copy {
x: T
}
impl<T> B<T> for $n<T> where T: Copy {
fn new(x: T) -> Self {
$n { x: x }
}
}
}
}
// tests/test_simple.rs
#[macro_use]
extern crate macro_test;
test!(Test);
#[test]
fn test_macro() {
let a = Test::<i32>::new(1);
}
In that case I get an error:
<macro_test macros>:2:54: 2:61 error: use of undeclared trait name `B` [E0405]
<macro_test macros>:2 struct $ n < T > where T : Copy { x : T } impl < T > B < T > for $ n < T >
If I rewrite the trait implementation with $crate variable:
impl<T> $crate::B<T> for $n<T> where T: Copy {
error message changes to next:
tests\test_simple.rs:8:13: 8:29 error: no associated item named `new` found for type `Test<i32>` in the current scope
tests\test_simple.rs:8 let a = Test::<i32>::new(1);
^~~~~~~~~~~~~~~~
tests\test_simple.rs:8:13: 8:29 help: items from traits can only be used if the trait is in scope; the following trait is implemented but not in scope, perhaps add a `use` for it:
tests\test_simple.rs:8:13: 8:29 help: candidate #1: use `macro_test::B`
Why does it happen?
Because you can't call trait methods without useing the trait. That's got nothing to do with macros—it's just a standard rule in Rust.
Maybe you want the macro to generate an inherent impl instead? i.e.
impl<T> $n<T> where T: Copy {
pub fn new(x: T) -> Self {
$n { x: x }
}
}
instead of what you have currently.
If I defined some enum and wanted to create a parser from string to that type, is there something better than just:
impl TheType {
fn from_str(s: &str) -> TheType {
// ...
}
}
The right way for converting from a string / parsing text is to implement the FromStr trait. For the example from the question it would look like this:
use std::str::FromStr;
enum Failure {
ReasonOne,
ReasonTwo,
}
impl FromStr for TheType {
type Err = Failure;
fn from_str(s: &str) -> Result<TheType, Self::Err> {
unimplemented!()
}
}
For generic conversion that cannot fail, you should implement the std::convert::From trait:
use std::convert::From;
#[derive(PartialEq, Eq, Debug)]
enum MyEnum {
One,
Two,
Many(i64),
}
impl From<i64> for MyEnum {
fn from(val: i64) -> Self {
match val {
1 => MyEnum::One,
2 => MyEnum::Two,
_ => MyEnum::Many(val),
}
}
}
fn main() {
assert_eq!(MyEnum::from(1), MyEnum::One);
assert_eq!(MyEnum::from(2), MyEnum::Two);
assert_eq!(MyEnum::from(3), MyEnum::Many(3));
}
Conveniently, implementing From also automatically implements Into:
let one: MyEnum = 1.into(); assert_eq!(one, MyEnum::One);
let two: MyEnum = 2.into(); assert_eq!(two, MyEnum::Two);
let many: MyEnum = 3.into(); assert_eq!(many, MyEnum::Many(3));
For potentially failing conversion, you should implement std::convert::TryFrom instead. It's only available in Rust 1.34 and up though, before these versions you can use the implementation in the conv crate.