Related
I'm getting blocked on what I think it's a simple problem. I'm still learning Rust, and I want to do the following:
I want to create an async trait (using async-trait) that will instantiate a DB connection instance and it will return the struct that is implementing that trait.
mongo.rs
#[async_trait]
pub trait DB {
async fn init<T, E>() -> Result<T, E>;
}
Then: favorites.rs (See the implementation of the DB trait down below)
use async_trait::async_trait;
use mongodb::Collection;
use rocket::form::FromForm;
use rocket::serde::ser::StdError;
use serde::{Deserialize, Serialize};
use std::error::Error;
use uuid::Uuid;
pub struct FavoritesDB {
collection: Collection<Favorite>,
}
#[derive(Debug)]
pub enum FavoritesError {
UnknownError(Box<dyn Error>),
}
// Conflicts with the one down below
// impl From<Box<dyn Error>> for FavoritesError {
// fn from(err: Box<dyn Error>) -> FavoritesError {
// FavoritesError::UnknownError(err)
// }
// }
impl From<Box<dyn StdError>> for FavoritesError {
fn from(err: Box<dyn StdError>) -> FavoritesError {
FavoritesError::UnknownError(err)
}
}
#[async_trait]
impl mongo::DB for FavoritesDB {
async fn init<FavoritesDB, FavoritesError>() -> Result<FavoritesDB, FavoritesError> {
let main_db = mongo::init::<Favorite>("Favorites").await?;
let db = FavoritesDB {
collection: main_db.collection,
};
Ok(db)
}
}
There are a list of problems with this:
1)
error[E0574]: expected struct, variant or union type, found type parameter `FavoritesDB`
--> src\db\favorites.rs:41:18
|
41 | let db = FavoritesDB {
| ^^^^^^^^^^^ not a struct, variant or union type
|
help: consider importing this struct instead
I've tried implementing From<Box<dyn tdError>> manually but it conflicts with what I have.
error[E0277]: `?` couldn't convert the error to `FavoritesError`
--> src\db\favorites.rs:40:65
|
40 | let main_db = mongo::init::<Favorite>("Favorites").await?;
| ^ the trait `From<Box<dyn StdError>>` is not implemented for `FavoritesError`
|
= note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait
= note: required because of the requirements on the impl of `FromResidual<Result<Infallible, Box<dyn StdError>>>` for `Result<FavoritesDB, FavoritesError>`
note: required by `from_residual`
--> C:\Users\asili\.rustup\toolchains\nightly-2021-11-15-x86_64-pc-windows-msvc\lib/rustlib/src/rust\library\core\src\ops\try_trait.rs:339:5
|
339 | fn from_residual(residual: R) -> Self;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: consider further restricting this bound
|
39 | async fn init<FavoritesDB, FavoritesError + std::convert::From<std::boxed::Box<dyn std::error::Error>>>() -> Result<FavoritesDB, FavoritesError> {
| ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Some errors have detailed explanations: E0277, E0282, E0574.
For more information about an error, try `rustc --explain E0277`.
Just for more context, here's the DB struct and impl (Currently connecting to a local MongoDB) included in mongo.rs
pub struct Database<T> {
client: mongodb::Database,
pub collection: Collection<T>,
}
impl<T> Database<T> {
pub async fn init() -> Result<mongodb::Database, Box<dyn Error>> {
let mut client_options = ClientOptions::parse("mongodb://localhost:27017").await?;
client_options.app_name = Some("My App".to_string());
// Get a handle to the deployment.
let client = Client::with_options(client_options)?;
let db = client.database("rust-svelte");
return Ok(db);
}
}
pub async fn init<T>(collection: &str) -> Result<Database<T>, Box<dyn Error>> {
let client = Database::<T>::init().await?;
let collection = client.collection::<T>(collection);
let db = Database { client, collection };
Ok(db)
}
I've been searching for a few days over SO and the Rust community and my Google-Rust-Fu isn't good enough to spot what's the problem. Any ideas?
You've declared init to take 2 generic parameters: T and E.
This means that the code that calls init has to provide the concrete types to fill in those parameters. For example, if someone was using your library, it would be totally feasible for them to write init::<i64, ()>(), and your code should deal with that.
Because of that, when you define your impl DB for FavouritesDB, you write this:
async fn init<FavoritesDB, FavoritesError>() -> Result<FavoritesDB, FavoritesError>
This is no different to writing:
async fn init<T, E>() -> Result<T, E>
you've just given the type parameters different names that happen to match a struct that you're probably trying to use.
A better pattern might be an associated type. Instead of the caller deciding what the concrete types are (as is the case with generics), with associated types, the implementation of the trait on the type sets the type.
This is common with things like Iterator. Iterator has no generic parameters, but a single associated type Item. This is because it wouldn't make sense to be able to impl Iterator<String> for MyStruct and impl Iterator<i64> for MyStruct at the same time. Instead, we want to implement Iterator for a type once, and that implementation carries with it the definition of the types it expects.
So something like this (I've omitted the async-ness for brevity since it doesn't seem to be a factor here):
trait DB {
type InitOk;
type InitErr;
fn init() -> Result<Self::InitOk, Self::InitErr>;
}
impl Db for FavouritesDB {
type InitOk = FavouritesDB;
type InitErr = FavouritesError;
fn init() -> Result<Self::InitOk, Self::InitErr> {
// now you can reference FavouritesDB the struct, rather than the generic parameter
}
}
I'd also add you may want to not have the InitOk type, and just return Self, but that's up to you if you think you might want a struct to be able to create a different type.
For part 2, Rust assumes nothing (other than Sized) about generic parameters. If you want Rust to force a generic to have some property, you have to add a bound.
The compiler is telling you here that it can't use the ? operator to convert automatically, because it doesn't know that your error type has a From<Box<dyn Error>> implementation.
If you know that every error type is going to implement that, you can add it as a bound on the associated type, like this:
trait DB {
type InitOk;
type InitErr: From<Box<dyn Error>>;
// ...
}
I am trying to understand following enum from this repo
#[repr(C)]
#[derive(BorshSerialize, BorshDeserialize, Debug, Clone)]
pub struct InitEscrowArgs {
pub data: EscrowReceive,
}
#[repr(C)]
#[derive(BorshSerialize, BorshDeserialize, Debug, Clone)]
pub struct ExchangeArgs {
pub data: EscrowReceive,
}
#[derive(BorshSerialize, BorshDeserialize, Clone)]
pub enum EscrowInstruction {
InitEscrow(InitEscrowArgs),
Exchange(ExchangeArgs),
CancelEscrow(),
}
and it's use of it in this match from this repo.
pub fn process(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
let instruction = EscrowInstruction::try_from_slice(instruction_data)?;
match instruction {
EscrowInstruction::InitEscrow(args) => {
msg!("Instruction: Init Escrow");
Self::process_init_escrow(program_id, accounts, args.data.amount)
}
EscrowInstruction::Exchange(args) => {
msg!("Instruction: Exchange Escrow");
Self::process_exchange(program_id, accounts, args.data.amount)
}
EscrowInstruction::CancelEscrow() => {
msg!("Instruction: Cancel Escrow");
Self::process_cancel(program_id, accounts)
}
}
}
I understand that this try_from_slice method gets some sort of byte array and deserialize it.
I do not understand how it determines which enum value to use.
The enum has 3 choices, InitEscrow / Exchange / CancelEscrow, but what determines the match to know which one it is suppose to select?
Seem to me the InitEscrowArgs and ExchangeArgs both takes in same struct. Both containing data that is EscrowReceive data type.
Method try_from_slice is part of the BorshDeserialize trait, which is derived on the enum in question. So, the choice between enum variants is made by the implementation of deserializer.
To see what is really going on, I've built the simplest possible example:
use borsh::BorshDeserialize;
#[derive(BorshDeserialize)]
enum Enum {
Variant1(u8),
Variant2,
}
By using cargo expand and a little manual cleanup, we can get the following equivalent code:
impl borsh::de::BorshDeserialize for Enum {
fn deserialize(buf: &mut &[u8]) -> Result<Self, std::io::Error> {
let variant_idx: u8 = borsh::BorshDeserialize::deserialize(buf)?;
let return_value = match variant_idx {
0u8 => Enum::Variant1(borsh::BorshDeserialize::deserialize(buf)?),
1u8 => Enum::Variant2,
_ => {
let msg = format!("Unexpected variant index: {}", variant_idx);
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
msg,
));
}
};
Ok(return_value)
}
}
Where the inner deserialize calls refers to impl BorshDeserialize for u8:
fn deserialize(buf: &mut &[u8]) -> Result<Self> {
if buf.is_empty() {
return Err(Error::new(
ErrorKind::InvalidInput,
ERROR_UNEXPECTED_LENGTH_OF_INPUT,
));
}
let res = buf[0];
*buf = &buf[1..];
Ok(res)
}
So, it works the following way:
Deserializer tries to pull one byte from input; if there's none - this is an error.
This byte is interpreted as an index of enum variant; if it doesn't match to one of variants - this is an error.
If the variant contains any data, deserializer tries to pull this data from the input; if it fails (according to the inner type's implementation) - this is an error.
An enum is clearly a kind of key/value pair structure. Consequently, it would be nice to automatically create a dictionary from one wherein the enum variants become the possible keys and their payload the associated values. Keys without a payload would use the unit value. Here is a possible usage example:
enum PaperType {
PageSize(f32, f32),
Color(String),
Weight(f32),
IsGlossy,
}
let mut dict = make_enum_dictionary!(
PaperType,
allow_duplicates = true,
);
dict.insert(dict.PageSize, (8.5, 11.0));
dict.insert(dict.IsGlossy, ());
dict.insert_def(dict.IsGlossy);
dict.remove_all(dict.PageSize);
Significantly, since an enum is merely a list of values that may optionally carry a payload, auto-magically constructing a dictionary from it presents some semantic issues.
How does a strongly typed Dictionary<K, V> maintain the discriminant/value_type dependency inherent with enums where each discriminant has a specific payload type?
enum Ta {
K1(V1),
K2(V2),
...,
Kn(Vn),
}
How do you conveniently refer to an enum discriminant in code without its payload (Ta.K1?) and what type is it (Ta::Discriminant?) ?
Is the value to be set and get the entire enum value or just the payload?
get(&self, key: Ta::Discriminant) -> Option<Ta>
set(&mut self, value: Ta)
If it were possible to augment an existing enum auto-magically with another enum of of its variants then a reasonably efficient solution seems plausible in the following pseudo code:
type D = add_discriminant_keys!( T );
impl<D> for Vec<D> {
fn get(&self, key: D::Discriminant) -> Option<D> { todo!() }
fn set(&mut self, value: D) { todo!() }
}
I am not aware whether the macro, add_discriminant_keys!, or the construct, D::Discriminant, is even feasible. Unfortunately, I am still splashing in the shallow end of the Rust pool, despite this suggestion. However, the boldness of its macro language suggests many things are possible to those who believe.
Handling of duplicates is an implementation detail.
Enum discriminants are typically functions and therefore have a fixed pointer value (as far as I know). If such values could become constants of an associated type within the enum (like a trait) with attributes similar to what has been realized by strum::EnumDiscriminants things would look good. As it is, EnumDiscriminants seems like a sufficient interim solution.
A generic implementation over HashMap using strum_macros crate is provided based on in the rust playground; however, it is not functional there due to the inability of rust playground to load the strum crate from there. A macro derived solution would be nice.
First, like already said here, the right way to go is a struct with optional values.
However, for completeness sake, I'll show here how you can do that with a proc macro.
When you want to design a macro, especially a complicated one, the first thing to do is to plan what the emitted code will be. So, let's try to write the macro's output for the following reduced enum:
enum PaperType {
PageSize(f32, f32),
IsGlossy,
}
I will already warn you that our macro will not support brace-style enum variants, nor combining enums (your add_discriminant_keys!()). Both are possible to support, but both will complicate this already-complicated answer more. I'll refer to them shortly at the end.
First, let's design the map. It will be in a support crate. Let's call this crate denum (a name will be necessary later, when we'll refer to it from our macro):
pub struct Map<E> {
map: std::collections::HashMap<E, E>, // You can use any map implementation you want.
}
We want to store the discriminant as a key, and the enum as the value. So, we need a way to refer to the free discriminant. So, let's create a trait Enum:
pub trait Enum {
type DiscriminantsEnum: Eq + Hash; // The constraints are those of `HashMap`.
}
Now our map will look like that:
pub struct Map<E: Enum> {
map: std::collections::HashMap<E::DiscriminantsEnum, E>,
}
Our macro will generate the implementation of Enum. Hand-written, it'll be the following (note that in the macro, I wrap it in const _: () = { ... }. This is a technique used to prevent names polluting the global namespaces):
#[derive(PartialEq, Eq, Hash)]
pub enum PaperTypeDiscriminantsEnum {
PageSize,
IsGlossy,
}
impl Enum for PaperType {
type DiscriminantsEnum = PaperTypeDiscriminantsEnum;
}
Next. insert() operation:
impl<E: Enum> Map<E> {
pub fn insert(discriminant: E::DiscriminantsEnum, value: /* What's here? */) {}
}
There is no way in current Rust to refer to an enum discriminant as a distinct type. But there is a way to refer to struct as a distinct type.
We can think about the following:
pub struct PageSize;
But this pollutes the global namespace. Of course, we can call it something like PaperTypePageSize, but I much prefer something like PaperTypeDiscriminants::PageSize.
Modules to the rescue!
#[allow(non_snake_case)]
pub mod PaperTypeDiscriminants {
#[derive(Clone, Copy)]
pub struct PageSize;
#[derive(Clone, Copy)]
pub struct IsGlossy;
}
Now we need a way in insert() to validate the the provided discriminant indeed matches the wanted enum, and to refer to its value. A new trait!
pub trait EnumDiscriminant: Copy {
type Enum: Enum;
type Value;
fn to_discriminants_enum(self) -> <Self::Enum as Enum>::DiscriminantsEnum;
fn to_enum(self, value: Self::Value) -> Self::Enum;
}
And here's how our macro will implements it:
impl EnumDiscriminant for PaperTypeDiscriminants::PageSize {
type Enum = PaperType;
type Value = (f32, f32);
fn to_discriminants_enum(self) -> PaperTypeDiscriminantsEnum { PaperTypeDiscriminantsEnum::PageSize }
fn to_enum(self, (v0, v1): Self::Value) -> Self::Enum { Self::Enum::PageSize(v0, v1) }
}
impl EnumDiscriminant for PaperTypeDiscriminants::IsGlossy {
type Enum = PaperType;
type Value = ();
fn to_discriminants_enum(self) -> PaperTypeDiscriminantsEnum { PaperTypeDiscriminantsEnum::IsGlossy }
fn to_enum(self, (): Self::Value) -> Self::Enum { Self::Enum::IsGlossy }
}
And now insert():
pub fn insert<D>(&mut self, discriminant: D, value: D::Value)
where
D: EnumDiscriminant<Enum = E>,
{
self.map.insert(
discriminant.to_discriminants_enum(),
discriminant.to_enum(value),
);
}
And trivially insert_def():
pub fn insert_def<D>(&mut self, discriminant: D)
where
D: EnumDiscriminant<Enum = E, Value = ()>,
{
self.insert(discriminant, ());
}
And get() (note: seprately getting the value is possible when removing, by adding a method to the trait EnumDiscriminant with the signature fn enum_to_value(enum_: Self::Enum) -> Self::Value. It can be unsafe fn and use unreachable_unchecked() for better performance. But with get() and get_mut(), that returns reference, it's harder because you can't get a reference to the discriminant value. Here's a playground that does that nonetheless, but requires nightly):
pub fn get_entry<D>(&self, discriminant: D) -> Option<&E>
where
D: EnumDiscriminant<Enum = E>,
{
self.map.get(&discriminant.to_discriminants_enum())
}
get_mut() is very similar.
Note that my code doesn't handle duplicates but instead overwrites them, as it uses HashMap. However, you can easily create your own map that handles duplicates in another way.
Now that we have a clear picture in mind what the macro should generate, let's write it!
I decided to write it as a derive macro. You can use an attribute macro too, and even a function-like macro, but you must call it at the declaration site of your enum - because macros cannot inspect code other than the code the're applied to.
The enum will look like:
#[derive(denum::Enum)]
enum PaperType {
PageSize(f32, f32),
Color(String),
Weight(f32),
IsGlossy,
}
Usually, when my macro needs helper code, I put this code in crate and the macro in crate_macros, and reexports the macro from crate. So, the code in denum (besides the aforementioned Enum, EnumDiscriminant and Map):
pub use denum_macros::Enum;
denum_macros/src/lib.rs:
use proc_macro::TokenStream;
use quote::{format_ident, quote};
#[proc_macro_derive(Enum)]
pub fn derive_enum(item: TokenStream) -> TokenStream {
let item = syn::parse_macro_input!(item as syn::DeriveInput);
if item.generics.params.len() != 0 {
return syn::Error::new_spanned(
item.generics,
"`denum::Enum` does not work with generics currently",
)
.into_compile_error()
.into();
}
if item.generics.where_clause.is_some() {
return syn::Error::new_spanned(
item.generics.where_clause,
"`denum::Enum` does not work with `where` clauses currently",
)
.into_compile_error()
.into();
}
let (vis, name, variants) = match item {
syn::DeriveInput {
vis,
ident,
data: syn::Data::Enum(syn::DataEnum { variants, .. }),
..
} => (vis, ident, variants),
_ => {
return syn::Error::new_spanned(item, "`denum::Enum` works only with enums")
.into_compile_error()
.into()
}
};
let discriminants_mod_name = format_ident!("{}Discriminants", name);
let discriminants_enum_name = format_ident!("{}DiscriminantsEnum", name);
let mut discriminants_enum = Vec::new();
let mut discriminant_structs = Vec::new();
let mut enum_discriminant_impls = Vec::new();
for variant in variants {
let variant_name = variant.ident;
discriminant_structs.push(quote! {
#[derive(Clone, Copy)]
pub struct #variant_name;
});
let fields = match variant.fields {
syn::Fields::Named(_) => {
return syn::Error::new_spanned(
variant.fields,
"`denum::Enum` does not work with brace-style variants currently",
)
.into_compile_error()
.into()
}
syn::Fields::Unnamed(fields) => Some(fields.unnamed),
syn::Fields::Unit => None,
};
let value_destructuring = fields
.iter()
.flatten()
.enumerate()
.map(|(index, _)| format_ident!("v{}", index));
let value_destructuring = quote!((#(#value_destructuring,)*));
let value_builder = if fields.is_some() {
value_destructuring.clone()
} else {
quote!()
};
let value_type = fields.into_iter().flatten().map(|field| field.ty);
enum_discriminant_impls.push(quote! {
impl ::denum::EnumDiscriminant for #discriminants_mod_name::#variant_name {
type Enum = #name;
type Value = (#(#value_type,)*);
fn to_discriminants_enum(self) -> #discriminants_enum_name { #discriminants_enum_name::#variant_name }
fn to_enum(self, #value_destructuring: Self::Value) -> Self::Enum { Self::Enum::#variant_name #value_builder }
}
});
discriminants_enum.push(variant_name);
}
quote! {
#[allow(non_snake_case)]
#vis mod #discriminants_mod_name { #(#discriminant_structs)* }
const _: () = {
#[derive(PartialEq, Eq, Hash)]
pub enum #discriminants_enum_name { #(#discriminants_enum,)* }
impl ::denum::Enum for #name {
type DiscriminantsEnum = #discriminants_enum_name;
}
#(#enum_discriminant_impls)*
};
}
.into()
}
This macro has several flaws: it doesn't handle visibility modifiers and attributes correctly, for example. But in the general case, it works, and you can fine-tune it more.
If you want to also support brace-style variants, you can create a struct with the data (instead of the tuple we use currently).
Combining enum is possible if you'll not use a derive macro but a function-like macro, and invoke it on both enums, like:
denum::enums! {
enum A { ... }
enum B { ... }
}
Then the macro will have to combine the discriminants and use something like Either<A, B> when operating with the map.
Unfortunately, a couple of questions arise in that context:
should it be possible to use enum types only once? Or are there some which might be there multiple times?
what should happen if you insert a PageSize and there's already a PageSize in the dictionary?
All in all, a regular struct PaperType is much more suitable to properly model your domain. If you don't want to deal with Option, you can implement the Default trait to ensure that some sensible defaults are always available.
If you really, really want to go with a collection-style interface, the closest approximation would probably be a HashSet<PaperType>. You could then insert a value PaperType::PageSize.
Wondering if there's a "proper" way of converting an Enum to a &str and back.
The problem I'm trying to solve:
In the clap crate, args/subcommands are defined and identified by &strs. (Which I'm assuming don't fully take advantage of the type checker.) I'd like to pass a Command Enum to my application instead of a &str which would be verified by the type-checker and also save me from typing (typo-ing?) strings all over the place.
This is what I came up with from searching StackOverflow and std:
use std::str::FromStr;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Command {
EatCake,
MakeCake,
}
impl FromStr for Command {
type Err = ();
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
match s.to_ascii_lowercase().as_str() {
"eat-cake" => Ok(Self::EatCake),
"make-cake" => Ok(Self::MakeCake),
_ => Err(()),
}
}
}
impl<'a> From<Command> for &'a str {
fn from(c: Command) -> Self {
match c {
Command::EatCake => "eat-cake",
Command::MakeCake => "make-cake",
}
}
}
fn main() {
let command_from_str: Command = "eat-cake".to_owned().parse().unwrap();
let str_from_command: &str = command_from_str.into();
assert_eq!(command_from_str, Command::EatCake);
assert_eq!(str_from_command, "eat-cake");
}
And here's a working playground:
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=b5e9ac450fd6a79b855306e96d4707fa
Here's an abridged version of what I'm running in clap.
let matches = App::new("cake")
.setting(AppSettings::SubcommandRequiredElseHelp)
// ...
.subcommand(
SubCommand::with_name(Command::MakeCake.into())
// ...
)
.subcommand(
SubCommand::with_name(Command::EatCake.into())
// ...
)
.get_matches();
It seems to work, but I'm not sure if I'm missing something / a bigger picture.
Related:
How to use an internal library Enum for Clap Args
How do I return an error within match statement while implementing from_str in rust?
Thanks!
The strum crate may save you some work. Using strum I was able to get the simple main() you have to work without any additional From implementations.
use strum_macros::{Display, EnumString, IntoStaticStr};
#[derive(Debug, Clone, Copy, PartialEq)]
#[derive(Display, EnumString, IntoStaticStr)] // strum macros.
pub enum Command {
#[strum(serialize = "eat-cake")]
EatCake,
#[strum(serialize = "make-cake")]
MakeCake,
}
fn main() {
let command_from_str: Command = "eat-cake".to_owned().parse().unwrap();
let str_from_command: &str = command_from_str.into();
assert_eq!(command_from_str, Command::EatCake);
assert_eq!(str_from_command, "eat-cake");
}
I've met a conflict with Rust's ownership rules and a trait object downcast. This is a sample:
use std::any::Any;
trait Node{
fn gen(&self) -> Box<Node>;
}
struct TextNode;
impl Node for TextNode{
fn gen(&self) -> Box<Node>{
Box::new(TextNode)
}
}
fn main(){
let mut v: Vec<TextNode> = Vec::new();
let node = TextNode.gen();
let foo = &node as &Any;
match foo.downcast_ref::<TextNode>(){
Some(n) => {
v.push(*n);
},
None => ()
};
}
The TextNode::gen method has to return Box<Node> instead of Box<TextNode>, so I have to downcast it to Box<TextNode>.
Any::downcast_ref's return value is Option<&T>, so I can't take ownership of the downcast result and push it to v.
====edit=====
As I am not good at English, my question is vague.
I am implementing (copying may be more precise) the template parser in Go standard library.
What I really need is a vector, Vec<Box<Node>> or Vec<Box<Any>>, which can contain TextNode, NumberNode, ActionNode, any type of node that implements the trait Node can be pushed into it.
Every node type needs to implement the copy method, return Box<Any>, and then downcasting to the concrete type is OK. But to copy Vec<Box<Any>>, as you don't know the concrete type of every element, you have to check one by one, that is really inefficient.
If the copy method returns Box<Node>, then copying Vec<Box<Node>> is simple. But it seems that there is no way to get the concrete type from trait object.
If you control trait Node you can have it return a Box<Any> and use the Box::downcast method
It would look like this:
use std::any::Any;
trait Node {
fn gen(&self) -> Box<Any>; // downcast works on Box<Any>
}
struct TextNode;
impl Node for TextNode {
fn gen(&self) -> Box<Any> {
Box::new(TextNode)
}
}
fn main() {
let mut v: Vec<TextNode> = Vec::new();
let node = TextNode.gen();
if let Ok(n) = node.downcast::<TextNode>() {
v.push(*n);
}
}
Generally speaking, you should not jump to using Any. I know it looks familiar when coming from a language with subtype polymorphism and want to recreate a hierarchy of types with some root type (like in this case: you're trying to recreate the TextNode is a Node relationship and create a Vec of Nodes). I did it too and so did many others: I bet the number of SO questions on Any outnumbers the times Any is actually used on crates.io.
While Any does have its uses, in Rust it has alternatives.
In case you have not looked at them, I wanted to make sure you considered doing this with:
enums
Given different Node types you can express the "a Node is any of these types" relationship with an enum:
struct TextNode;
struct XmlNode;
struct HtmlNode;
enum Node {
Text(TextNode),
Xml(XmlNode),
Html(HtmlNode),
}
With that you can put them all in one Vec and do different things depending on the variant, without downcasting:
let v: Vec<Node> = vec![
Node::Text(TextNode),
Node::Xml(XmlNode),
Node::Html(HtmlNode)];
for n in &v {
match n {
&Node::Text(_) => println!("TextNode"),
&Node::Xml(_) => println!("XmlNode"),
&Node::Html(_) => println!("HtmlNode"),
}
}
playground
adding a variant means potentially changing your code in many places: the enum itself and all the functions that do something with the enum (to add the logic for the new variant). But then again, with Any it's mostly the same, all those functions might need to add the downcast to the new variant.
Trait objects (not Any)
You can try putting the actions you'd want to perform on the various types of nodes in the trait, so you don't need to downcast, but just call methods on the trait object.
This is essentially what you were doing, except putting the method on the Node trait instead of downcasting.
playground
The (more) ideomatic way for the problem:
use std::any::Any;
pub trait Nodeable {
fn as_any(&self) -> &dyn Any;
}
#[derive(Clone, Debug)]
struct TextNode {}
impl Nodeable for TextNode {
fn as_any(&self) -> &dyn Any {
self
}
}
fn main() {
let mut v: Vec<Box<dyn Nodeable>> = Vec::new();
let node = TextNode {}; // or impl TextNode::new
v.push(Box::new(node));
// the downcast back to TextNode could be solved like this:
if let Some(b) = v.pop() { // only if we have a nodeā¦
let n = (*b).as_any().downcast_ref::<TextNode>().unwrap(); // this is secure *)
println!("{:?}", n);
};
}
*) This is secure: only Nodeables are allowd to be downcasted to types that had Nodeable implemented.