I want to build a class in Rust that allows for variety and convenience in its instantiation similar to the way that std::net::TcpStream can be provided multiple inputs to its connect method.
I have the following tests, but very little idea of the structs or traits I need to build to achieve this result.
#[cfg(test)]
mod test {
use super::Ipv4Network;
use std::net::Ipv4Addr;
#[test]
fn instantiation() {
let net_addr = Ipv4Addr::new(10, 0, 0, 0);
let net_mask = Ipv4Addr::new(255, 255, 255, 240);
// Instantiate an Ipv4Network using two Ipv4Addrs.
let with_addrs = Ipv4Network::new(net_addr, net_mask);
// Instantiate an Ipv4Network using an Ipv4Addr and a prefix integer.
let with_addr_prefix = Ipv4Network::new(net_addr, 28);
// Instantiate an Ipv4Network using two strings.
let with_strings = Ipv4Network::new("10.0.0.0", "255.255.255.240");
// Instantiate an Ipv4Network using a string and a prefix integer.
let with_prefix = Ipv4Network::new("10.0.0.0", 28);
// Instantiate an Ipv4Network using a string with CIDR notation.
let with_cidr = Ipv4Network::new("10.0.0.0/28");
}
}
My understanding is that std::net::TcpStream accomplishes its multiple connect inputs through the ToSocketAddrs trait, the source of which can be found here. How exactly std::net:TcpStream::connect uses ToSocketAddrs isn't clear to me, but I imagine I should be able to define an Ipv4Network as follows:
pub struct Ipv4Network {
addr: Ipv4Addr,
mask: Ipv4Addr,
}
If that's true then how do I go about creating a trait (say ToIpv4Network) that functions like ToSocketAddrs does for std::net:TcpStream?
What should the meat of
impl ToIpv4Network for (Ipv4Addr, Ipv4Addr) {
...
}
look like? Is that even the right path to start going down?
That's a reasonable place to start working from. I think one of your issues is that the interfaces you have defined are going to be difficult to accomplish as written by using a trait.
So, let's talk about the way that ToSocketAddrs works. First, let's just quickly note and then ignore the fact that the ToSocketAddrs trait returns an Iterator of SocketAddrs. We're ignoring it because it's actually irrelevant to the question at hand.
Here's the body of the trait declaration:
pub trait ToSocketAddrs {
type Iter: Iterator<Item=SocketAddr>;
fn to_socket_addrs(&self) -> Result<Self::Iter>;
}
The purpose of this trait is to allow it to be used as a trait bound in a generic function/struct. Broadly speaking, trait bounds are a way of writing a generic but getting the Rust compiler to ensure that the type that ends up instantiating the generic implements the behavior defined by that trait (or traits.
In the case of ToSocketAddrs this means that a type can (potentially) be converted to a SocketAddr. The code that is called inside of connect then makes use of the to_socket_addrs function defined on the trait.
As a brief aside, I said potentially because to_socket_addrs returns a Result which can take on values of Ok(<success-type>) and Err(<failure-type>), which is probably a good pattern to model your trait on.
This is the key point that relates to why I think your desired interface is maybe not ideal (or implementable, Rust doesn't directly support arity, or any other type of function overloading). Passing a trait bound generic parameter is the easiest way to accomplish your goal.
Related
Say I have a trait that looks like this:
use std::{error::Error, fmt::Debug};
use super::CheckResult;
/// A Checker is a component that is responsible for checking a
/// particular aspect of the node under investigation, be that metrics,
/// system information, API checks, load tests, etc.
#[async_trait::async_trait]
pub trait Checker: Debug + Sync + Send {
type Input: Debug;
/// This function is expected to take input, whatever that may be,
/// and return a vec of check results.
async fn check(&self, input: &Self::Input) -> anyhow::Result<Vec<CheckResult>>;
}
And say I have two implementations of this trait:
pub struct ApiData {
some_response: String,
}
pub MetricsData {
number_of_events: u64,
}
pub struct ApiChecker;
impl Checker for ApiChecker {
type Input = ApiData;
// implement check function
}
pub struct MetricsChecker;
impl Checker for MetricsChecker {
type Input = MetricsData;
// implement check function
}
In my code I have a Vec of these Checkers that looks like this:
pub struct MyServer {
checkers: Vec<Box<dyn Checker>>,
}
What I want to do is figure out, based on what Checkers are in this Vec, what data I need to fetch. For example, if it just contained an ApiChecker, I would only need to fetch the ApiData. If both ApiChecker and MetricsChecker were there, I'd need both ApiData and MetricsData. You can also imagine a third checker where Input = (ApiData, MetricsData). In that case I'd still just need to fetch ApiData and MetricsData once.
I imagine an approach where the Checker trait has an additional function on it that looks like this:
fn required_data(&self) -> HashSet<DataId>;
This could then return something like [DataId::Api, DataId::Metrics]. I would then run this for all Checkers in my vec and then I'd end up a complete list of data I need to get. I could then do some complicated set of checks like this:
let mut required_data = HashSet::new();
for checker in checkers {
required_data.union(&mut checker.required_data());
}
let api_data: Option<ApiData> = None;
if required_data.contains(DataId::Api) {
api_data = Some(get_api_data());
}
And so on for each of the data types.
I'd then pass them into the check calls like this:
api_checker.check(
api_data.expect("There was some logic error and we didn't get the API data even though a Checker declared that it needed it")
);
The reasons I want to fetch the data outside of the Checkers is:
To avoid fetching the same data multiple times.
To support memoization between unrelated calls where the arguments are the same (this could be done inside some kind of Fetcher trait implementation for example).
To support generic retry logic.
By now you can probably see that I've got two big problems:
The declaration of what data a specific Checker needs is duplicated, once in the function signature and again from the required_data function. This naturally introduces bug potential. Ideally this information would only be declared once.
Similarly, in the calling code, I have to trust that the data that the Checkers said they needed was actually accurate (the expect in the previous snippet). If it's not, and we didn't get data we needed, there will be problems.
I think both of these problems would be solved if the function signature, and specifically the Input associated type, was able to express this "required data" declaration on its own. Unfortunately I'm not sure how to do that. I see there is a nightly feature in any that implements Provider and Demand: https://doc.rust-lang.org/std/any/index.html#provider-and-demand. This sort of sounds like what I want, but I have to use stable Rust, plus I figure I must be missing something and there is an easier way to do this without going rogue with semi dynamic typing.
tl;dr: How can I inspect what types the arguments are for a function (keeping in mind that the input might be more complex than just one thing, such as a struct or tuple) at runtime from outside the trait implementer? Alternatively, is there a better way to design this code that would eliminate the need for this kind of reflection?
Your problems start way earlier than you mention:
checkers: Vec<Box<dyn Checker>>
This is an incomplete type. The associated type Input means that Checker<Input = ApiData> and Checker<Input = MetricsData> are incompatible. How would you call checkers[0].check(input)? What type would input be? If you want a collection of "checkers" then you'll need a unified API, where the arguments to .check() are all the same.
I would suggest a different route altogether: Instead of providing the input, provide a type that can retrieve the input that they ask for. That way there's no need to coordinate what type the checkers will ask for in a type-safe way, it'll be inherent to the methods the checkers themselves call. And if your primary concern is repeatedly retrieving the same data for different checkers, then all you need to do is implement caching in the provider. Same with retry logic.
Here's my suggestion:
struct DataProvider { /* cached api and metrics */ }
impl DataProvider {
fn fetch_api_data(&mut self) -> anyhow::Result<ApiData> { todo!() }
fn fetch_metrics_data(&mut self) -> anyhow::Result<MetricsData> { todo!() }
}
#[async_trait::async_trait]
trait Checker {
async fn check(&self, data: &mut DataProvider) -> anyhow::Result<Vec<CheckResult>>;
}
struct ApiAndMetricsChecker;
#[async_trait::async_trait]
impl Checker for ApiAndMetricsChecker {
async fn check(&self, data: &mut DataProvider) -> anyhow::Result<Vec<CheckResult>> {
let _api_data = data.fetch_api_data()?;
let _metrics_data = data.fetch_metrics_data()?;
// do something with api and metrics data
todo!()
}
}
I'd like to save a record from PalletA in PalletB by simply passing the raw data and waiting for the return.
I have tried following:
// ./PalletB/lib.rs
pub trait PutInStorage {
fn put_rule_in_storage(value: u32);
}
impl<T: Trait> PutInStorage for Module<T> {
fn put_rule_in_storage(value: u32) {
SimpleCounter::put(value);
}
}
then in
// ./PalletA/lib.rs
use palletB::{PutInStorage, Trait as PalletBTrait};
///The pallet's configuration trait.
pub trait Trait: system::Trait + PalletBTrait {
/// The overarching event type.
type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
type ExternalStorage: PutInStorage;
}
then I added the definition to the runtime like this:
// ./runtime/lib.rs
// near the construct_runtime macro
impl palletA::Trait for Runtime {
type Event = Event;
type ExternalStorage = palletB::Module<Runtime>;
}
So far this passes the check but not the test. The test configuration for the trait is like this:
use palletB::{PutInStorage, Trait as PalletBTrait};
impl Trait for Test {
type Event = ();
type ExternalStorage = PutInStorage;
}
and this fails with the:
type ExternalRulesStorage = PutInStorage;
^^^^^^^^^^^^ help: use `dyn`: `dyn PutInStorage`
impl Trait for Test
------------------- in this `impl` item
type Event = ();
type ExternalRulesStorage = PutInStorage;
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
type ExternalRulesStorage = PutInStorage;
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `pallet_rules::PutInStorage` cannot be made into an object
I tried all the suggestions the Rust compiler gives me but without any luck. Before someone asks why do I need this in my test, it's because a dispatchable fn in decl_module! checks does a certain record exists before starts processing and saving its own records. It is dependent on record to exist.
To make the compiler happy, your test config must also have an instance of PalletB, or anything else that implements PutInStorage.
Similar to what you've done already in ./runtime/lib.rs:
impl Trait for Test {
type Event = ();
type ExternalStorage = palletB::Module<Test>;
}
Note that now struct Test is the one playing the role of Runtime. I think this is the only thing that you are missing.
That being said, you seem to be on the wrong track in the overall design.
PalletA already depends on PalletB. Given that you also have a trait to link the two PutInStorage, this is not a good design. Generally, you should try and always chose one of the following:
Two pallets will depend on one another. In this case you don't need traits. If one needs to put something to the storage of the other one, you just do it directly. In your example, I assume that PalletB has a storage item called pub Foo: u32 in decl_storage and PutInStorage writes to this. If this is the case, there's no need for a trait. From PalletA you can just say: palletB::Foo::put(value).
Note that this approach should be chosen with care, otherwise you might end up with a lot of pallets depending on one another which is not good.
You decide that your pallets do NOT depend on one another, in which case you use some trait like PutInStorage. Your code seem to be aligned with this approach, except you define PalletA's Trait as pub trait Trait: system::Trait. There's no need to depend on PalletB here, and of course you can wipe it from Cargo.toml as well.
I need to implement the fmt::Display method for an object coming from an external crate, so I created a wrapper for this object. I'd like to be able to use all the methods from the original object, without having to redefine all of them. I tried to implement Deref as advised on the awesome IRC channel #rust-beginners:
struct CustomMap(ObjectComingFromAnExternalCrate<char, char>);
impl std::ops::Deref for CustomMap {
type Target = ObjectComingFromAnExternalCrate<char, char>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
fn main() {
let cm = CustomMap::with_capacity(10);
println!("Hello, world!");
}
However, I'm getting this error :
error: no associated item named `with_capacity` found for type `CustomMap` in the current scope
--> <anon>:16:13
|
16 | let a = CustomMap::with_capacity(10);
| ^^^^^^^^^^^^^^^^^^^^^^^^
I assume it's because deref() doesn't work with associated functions.
How can I work around this? Reimplementing every associated function I use, just to be able to implement one method I need seems like overkill.
Newtypes are specifically designed to provide encapsulation, so they do not necessarily lend them well to just "adding new stuff".
That being said, a combination of:
Deref and DerefMut to get access to the methods
From and Into to easily convert from one to the other
OR making the inner type pub
should be able to tackle this.
The From/Into recommendation comes from the fact that most associated functions are generally constructors1.
impl From<ObjectComingFromAnExternalCrate<char, char>> for CustomMap { ... }
and then you can do:
let cm: CustomMap = ObjectComingFromAnExternalCrate<char, char>::with_capacity(10).into();
The other solution is to define CustomMap as:
struct CustomMap(pub ObjectComingFromAnExternalCrate<char, char>);
and then:
let cm = CustomMap(ObjectComingFromAnExternalCrate<char, char>::with_capacity(10));
If you do not wish to enforce any other invariant, and do not care about encapsulation, either should get you going.
1 Pointer types, such as Rc, use them heavily to avoid hiding methods of the Deref'ed to type.
I am currently writing a lint to find places where the use of std::borrow::Cow may be beneficial. Those places may include local variables, but also struct fields and enum arguments, unless they are part of the public interface (at which point I intend to bail; I don't want to ask people to change official interfaces at this point).
However, to do this, we have to check the whole crate for definitions before reporting. Since the LintPass trait has no callback that is called unconditionally after the crate has been walked, I am trying to implement rustc_front::visit::Visitor by my own visitor struct that encapsulates the rustc::lint::Context and our data.
Furthermore, I want to check fields of type String which are somewhere instantiated from a &'static str. To make this feasible, I'd like to use the ExprUseVisitor trait with my visitor struct.
The code as of now is here.
I get the following error:
src/cow.rs:56:44: 56:48 error: cannot infer an appropriate lifetime for lifetime parameter `'v` due to conflicting requirements
src/cow.rs:56 let vis = euv::ExprUseVisitor::new(self as &mut euv::Delegate<'t>, &infcx);
^~~~
src/cow.rs:51:5: 58:6 help: consider using an explicit lifetime parameter as shown: fn visit_fn(&mut self, _: FnKind, fd: &FnDecl, b: &Block, _: Span, id: NodeId)
src/cow.rs:51 fn visit_fn(&mut self, _: FnKind, fd: &FnDecl, b: &Block,
src/cow.rs:52 _: Span, id: NodeId) {
src/cow.rs:53 let tcx = &self.cx.tcx;
src/cow.rs:54 let param_env = Some(ty::ParameterEnvironment::for_item(tcx, id));
src/cow.rs:55 let infcx = infer::new_infer_ctxt(tcx, &tcx.tables, param_env, false);
src/cow.rs:56 let vis = euv::ExprUseVisitor::new(self as &mut euv::Delegate<'t>, &infcx);
...
This error is quite surprising, to say the least. Note that the suggested "explicit lifetime" is actually the same as my code.
So how do I get my code to compile?
The problem is in your implementation of euv::Delegate. Specifically, you tried to implement euv::Delegate<'v> for CowVisitor<'v, 't> when you meant to implement euv::Delegate<'t> for CowVisitor<'v, 't>.
In general, if you're doing anything complicated with lifetimes, the error messages are nearly useless; don't trust that they're actually pointing at the error.
When compiling the following code:
trait RenderTarget {}
struct RenderWindow;
impl RenderTarget for RenderWindow {}
trait Drawable {
fn draw<RT: RenderTarget>(&self, target: &mut RT);
}
fn main() {
let mut win = RenderWindow;
let mut vec: Vec<Box<Drawable>> = Vec::new();
for e in &vec {
e.draw(&mut win);
}
}
I get the error:
error: the trait `Drawable` is not implemented for the type `Drawable` [E0277]
src/main.rs:15 e.draw(&mut win);
^~~~~~~~~~~~~~
What is the error message trying to tell? Also, how to fix it?
There's a related question but the solution there was to modify the trait A (which corresponds to Drawable in my case), but that's not possible here since Drawable is from an external library.
Update: fixed object safety rules to the 1.0 version of them. Namely, by-value self makes method object-unsafe no longer.
This error happens because of object safety.
In order to be able to create a trait object out of a trait, the trait must be object-safe. A trait is object-safe if both of these statements hold:
it does not have Sized requirement, as in trait Whatever: Sized {};
all its methods are object-safe.
A method is object-safe if both of these statements are true:
it has where Self: Sized requirement, as in fn method() where Self: Sized;
none of the following statements holds:
this method mentions Self in their signature in any form, even under a reference, except associated types;
this method is static;
this method is generic.
These restrictions are in fact fairly natural if you think more of them.
Remember that when values are made into trait objects, actual information of their type is erased, including their size. Therefore, trait objects can only be used through a reference. References (or other smart pointers, like Box or Rc), when applied to trait objects, become "fat pointers" - along with the pointer to the value, they also contain a pointer to the virtual table for that value.
Because trait objects can only be used through a pointer, by-value self methods can't be called on them - you'd need the actual value in order to call such methods. This was a violation of object safety at one point, which meant that traits with such methods couldn't be made trait objects, however, even before 1.0 the rules had been tweaked to allow by-value self methods on trait objects. These methods still can't be called, though, due to the reason described above. There are reasons to expect that in the future this restriction will be lifted because it currently leads to some quirks in the language, for example, the inability to call Box<FnOnce()> closures.
Self can't be used in methods which should be called on trait objects precisely because trait objects have their actual type erased, but in order to call such methods the compiler would need to know this erased type.
Why static methods can't be called on trait objects, I guess, is obvious - static methods by definition "belong" to the trait itself, not to the value, so you need to know the concrete type implementing the trait to call them. More concretely, regular methods are dispatched through a virtual table stored inside a trait object, but static methods do not have a receiver, so they have nothing to dispatch on, and for this reason they can't be stored in a virtual table. Thus they are uncallable without knowing the concrete type.
Generic trait methods can't be called for another reason, more technical than logical, I think. In Rust generic functions and methods are implemented through monomorphization - that is, for each instantiation of a generic function with a concrete set of type parameters the compiler generate a separate function. For the language user it looks like that they're calling a generic function; but on the lowest level for each set of type parameters there is a separate copy of the function, specialized to work for the instantiated types.
Given this approach, in order to call generic methods on a trait object you would need its virtual table to contain pointers to virtually each and every possible instantiation of the generic method for all possible types, which is, naturally, impossible because it would require infinite number of instantiations. And so calling generic methods on trait objects is disallowed.
If Drawable is an external trait, then you're stuck - it is impossible to do what you want, that is, to call draw() on each item in a heterogeneous collection. If your set of drawables is statically known, you can create a separate collection for each drawable type or, alternatively, create your own enum which would contain a variant for each drawable type you have. Then you can implement Drawable for the enum itself, which would be fairly straightforward.
I refer to Vladimir's excellent answer which explains Object's safety, however I am afraid than in the middle of the discussion the concrete problem at hand was forgotten.
As Vladimir mentions, the issue is that a method generic over types (generic over lifetimes is fine) renders the trait it belongs to unusable for run-time polymorphism; this, in Rust, is called Object Safety.
The simplest fix, therefore, is to remove the generic parameter of the method!
trait RenderTarget {}
struct RenderWindow;
impl RenderTarget for RenderWindow {}
trait Drawable {
fn draw(&self, target: &mut RenderTarget);
}
fn main() {
let mut win = RenderWindow;
let mut vec: Vec<Box<Drawable>> = Vec::new();
for e in &vec {
e.draw(&mut win);
}
}
The main difference between:
fn draw<RT: RenderTarget>(&self, target: &mut RT)
and
fn draw(&self, target: &mut RenderTarget)
is that the latter requires RenderTarget to be Object Safe too as it is now used in a run-time polymorphism situation (so, no static method, no generic method, no Self, ...).
Another (more technical) difference is that the former is "monorphised" at compile-time (that is RT is substituted with the real type and all relevant optimizations applied) whereas the latter is not (and so, no such optimizations occur).
If you're stuck with what you're given, there are two options you could try.
In this case, you can't, but if you were given an unsized RenderTarget
trait Drawable {
fn draw<RT: RenderTarget + ?Sized>(&self, target: &mut RT);
}
you could implement
trait DrawableDynamic {
fn draw(&self, target: &mut RenderTarget);
}
impl<T: Drawable> DrawableDynamic for T {
fn draw(&self, target: &mut RenderTarget) {
Drawable::draw(self, target)
}
}
to redirect the types you're given to an object-safe dynamically dispatched alternative. It looks like such a change could be made upstream, since you can't really use the fact that RT is sized.
The other doesn't allow you to put arbitrary Drawables in your Vec, but should work without allowing unsized types upstream. This is to use an enum to wrap the possible values of the vector:
enum AllDrawable {
Square(Square),
Triangle(Triangle)
}
impl Drawable for AllDrawable {
fn draw<RT: RenderTarget>(&self, target: &mut RT) {
match *self {
AllDrawable::Square(ref x) => x.draw(target),
AllDrawable::Triangle(ref x) => x.draw(target),
}
}
}
One might want to add From implementations and such; you might find it easier if using wrapped_enum! which will automatically implement those for you.