Heterogenous containers in Rust for a graph - rust

I am a C++ programmer learning Rust, and one of my main use cases is a graph-based computation engine. In my graph I have store a homogeneous type, and then I derive from this with a more specific type e.g. in C++
class BaseNode {
public:
BaseNode(std::vector<std::shared_ptr<BaseNode>>& parents);
virtual ~BaseNode() = default;
virtual void update();
const std::vector<std::shared_ptr<BaseNode>>& parents() const;
...
};
template<typename T>
class TypedNode<T> : public BaseNode {
public:
const T& value() const { return value_; }
...
private:
T value_;
}
The idea is that the graph is traversed and update() is called on each node. The node knows what each of its parents "true type" is and so in its update() can do something like static_cast<TypedNode<DataBlob>>(parents()[0]).
How do I achieve something like this in Rust?
I thought about having a design like this:
trait BaseNode {
fn parents(&self) -> &Vec<dyn BaseNode>;
}
trait TypedNode<T>: BaseNode {
fn value(&self) -> &T;
}
But I read that I won't be able to cast the "trait object" from a BaseNode into a TypedNode<T>. (Or can I do it somehow using unsafe?). The other alternative I thought would be to have a struct that stores the data in Any and then to cast that, but does that incur some runtime cost?

If all node's parents have the same type then you can use that approach:
trait BaseNode {
type Parent: BaseNode;
fn parents(&self) -> &[Self::Parent];
}
trait TypedNode<P: BaseNode>: BaseNode<Parent = P> {
type ValueType;
fn value(&self) -> &Self::ValueType;
}
Rust playground
I'm not sure if I understand your question. Please let me know if it doesn't work for you.

Related

Rust - Typescript - Keyof

I'm new to Rust and just wondering if there's an equivalent of the keyof (like in TypeScript) operator in Rust.
I don't know if this is possible, but I'm trying to access the key and value of a struct within another struct.
example:
interface Events {
msg:(data:string)=>any,
abort:()=>any
}
class EventEmitter<T>{
on(event: keyof T,callback:T[keyof T])
}
I'm trying to achieve the same on function in rust.
struct Events {
msg: Fn(&str)->(),
abort: Fn()->(),
}
struct EventEmitter<T> {
pub listeners: Vec<Listener<T>>,
}
Context: I'm trying to recreate EventEimtter exactly like node.js & ts
What you're describing is reflection. As mentioned in the comments to your question, Rust does not have reflection as a native feature. Using a String to access members of your struct could potentially create unpredictable or undefined behavior.
If it truly is important to access members of your struct that have the same type, you could look into creating a trait called "Keyable" or something similar. This struct should (probably) look something like this.
pub trait Keyable<T> {
fn get_string(&self, for_key: T) -> Option<&String>;
fn get_i32(&self, key: T) -> Option<&i32>;
}
pub enum UserKeys {
Id,
Username,
Password
}
pub struct User {
id: i32,
username: String,
password: String
}
impl Keyable<UserKeys> for User {
fn get_string(&self, key: UserKeys) -> Option<&String> {
match key {
UserKeys::Username => Some(&self.username),
UserKeys::Password => Some(&self.password),
_ => None
}
}
fn get_i32(&self, key: UserKeys) -> Option<&i32> {
match key {
UserKeys::Id => Some(&self.id),
_ => None
}
}
}
This would create a valid implementation of reflection in Rust. It is worth noting that you would not necessarily have to type all of this by hand; you could look into creating a Derive macro (Rust Book).
You could then add a type bound to your EventEmitter so it becomes:
struct EventEmitter<K, T: Keyable<K>> {
pub listeners: Vec<Listener<T>>,
}
This code says "I want to create a struct that can hold many instances of a Listener for type Keyable (T) with a certain key type (K).
There would still be quite a bit of work to do in order to get your events all connected, but taking care of reflection is a big step.
This is an example I've written that allows a derivation of a struct called ToJson. It allows all implementors to automatically inherit a to_json function that creates a String of all its properties. GitHub

JS style static property for Rust struct

In javascript we can use the static keyword to define a static method or property for a class. Neither static methods nor static properties can be called on instances of the class. Instead, they're called on the class itself. So, for instance, we could count the number of instances of a certain class we've created:
class Player{
static playerCount = 0;
constructor(){
Player.playerCount ++;
}
}
Is their anything in rust that would roughly equivalent to this? Or possibly a library/macro that allows for something similar?
struct Player{}
impl Player{
static playerCount;
pub fn new()->Self{
playerCount ++
//increment playerCount
}
}
There isn't such a thing as a static field in a Rust struct. Rust's structs are closer to their C namesake than the classes you'd find in object-oriented languages.
The rough equivalent is probably a static mut variable, which requires the use of unsafe code. This is because a globally accessible, mutable value is going to be a source of undefined behavior (such as data races) and Rust wants all code to be free of UB unless you write the word unsafe.
For your use case, perhaps a PlayerManager struct which holds this state and is passed by &mut reference to the new function is a good idea.
struct PlayerManager {
count: usize,
}
impl PlayerManager {
pub fn add_player(&mut self) {
self.count += 1;
}
}
// then...
impl Player {
pub fn new(manager: &mut PlayerManager) -> Self {
manager.add_player();
// TODO: the rest of player construction
}
}

What is a "nominal type" in the context of an inherent implementation?

I am currently going through the Rust Documentation to understand inherent implementations. What is the "nominal type" and what are they referring to when then they say "associable items to the implementing type"?
Is there a related analog to this in C or C++?
Well, that's the language reference. Learning Rust with that is certainly possible, but a little bit like trying to learn English by reading a dictionary. Have you tried the Rust Book?
Anyway, as the first paragraph states, the "nominal type" is, well:
impl /* --> */ Point /* <-- this is the "nominal type" */ {
fn log(&self) { ... }
}
It's the type which is the subject of the inherent impl. An "associable item" is an item (like a fn, const, or type) which is associated with the nominal type.
If you had the paragraph:
Let's talk about Raymond. His hair is brown. He knows how to dance.
That would be roughly equivalent to:
struct Raymond; // introduce the Raymond type.
impl Raymond { // associate some items with Raymond.
const HAIR: Colour = Colour::Brown;
fn dance(&mut self) { ... }
}
fn main() {
let mut ray = Raymond;
println!("Raymond's hair is {}", Raymond::HAIR);
ray.dance();
}
(As an aside: the pronouns in the paragraph (like "he" or "him") would become self or Self in the impl.)
The impl is associating those items with the nominal type. Notice how HAIR is "inside" of the Raymond type. You could also write the above code as:
struct Raymond;
const RAYMOND_HAIR: Colour = Colour::Brown;
fn raymond_dance(ray: &mut Raymond) { ... }
fn main() {
let mut ray = Raymond;
println!("Raymond's hair is {}", RAYMOND_HAIR);
raymond_dance(&mut ray);
}
Here, there're no inherent impls, so the RAYMOND_HAIR and raymond_dance items aren't associated with the Raymond type directly. There's no fundamental difference between the two, other than convenience.
As for tying this back to C++... that's tricky since Rust distinguishes between inherent and non-inherent impls and C++... doesn't. The closest analogue would be to say that they're like the parts of a struct body that aren't fields and aren't overriding methods in a base class.
An inherent implementation is the equivalent of creating a class in a OOP language. The difference in Rust is that data is separated from implementation:
/* Data */
struct Foo {
// all data there
//...
}
/* Inherent implementation */
impl Foo {
fn bar(&self) {
//...
}
}
The nominal type is the data that you implement.
The associable items are the methods that you add to the data. Those functions are special because you can call them with the syntax foo.bar().
The inherent implementation is called like that as opposed to trait implementation:
/* Trait implementation */
impl Debug for Foo {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
//...
}
}
In the case of inherent implementation, the method is bound to the data only. It has a sense only with this data.
In the case of a trait implementation, the method can be implemented for any data that implements the trait.
The equivalent in C++ could be:
struct Debug {
virtual std::string fmt() = 0;
}
class Foo: public Debug {
// all data there
//...
public:
/* Equivalent of inherent implementation */
void bar() {
//...
}
/* Equivalent of trait implementation:
implementation of base class */
std::string fmt() {
//...
}
}
In C++, you cannot separate "inherent implementation" from "trait implementation" (I put those between quotes, because those terms do not make sense in C++, of course).
Note that unlike in C++, in Rust the methods are not really different that a free function. You can call the bar method like this:
Foo::bar(foo);
and if you define this function:
fn qux(f: &Foo) {
//...
}
it will have the same signature as Foo::bar.

Convenient 'Option<Box<Any>>' access when success is assured?

When writing callbacks for generic interfaces, it can be useful for them to define their own local data which they are responsible for creating and accessing.
In C I would just use a void pointer, C-like example:
struct SomeTool {
int type;
void *custom_data;
};
void invoke(SomeTool *tool) {
StructOnlyForThisTool *data = malloc(sizeof(*data));
/* ... fill in the data ... */
tool.custom_data = custom_data;
}
void execute(SomeTool *tool) {
StructOnlyForThisTool *data = tool.custom_data;
if (data.foo_bar) { /* do something */ }
}
When writing something similar in Rust, replacing void * with Option<Box<Any>>, however I'm finding that accessing the data is unreasonably verbose, eg:
struct SomeTool {
type: i32,
custom_data: Option<Box<Any>>,
};
fn invoke(tool: &mut SomeTool) {
let data = StructOnlyForThisTool { /* my custom data */ }
/* ... fill in the data ... */
tool.custom_data = Some(Box::new(custom_data));
}
fn execute(tool: &mut SomeTool) {
let data = tool.custom_data.as_ref().unwrap().downcast_ref::<StructOnlyForThisTool>().unwrap();
if data.foo_bar { /* do something */ }
}
There is one line here which I'd like to be able to write in a more compact way:
tool.custom_data.as_ref().unwrap().downcast_ref::<StructOnlyForThisTool>().unwrap()
tool.custom_data.as_ref().unwrap().downcast_mut::<StructOnlyForThisTool>().unwrap()
While each method makes sense on its own, in practice it's not something I'd want to write throughout a code-base, and not something I'm going to want to type out often or remember easily.
By convention, the uses of unwrap here aren't dangerous because:
While only some tools define custom data, the ones that do always define it.
When the data is set, by convention the tool only ever sets its own data. So there is no chance of having the wrong data.
Any time these conventions aren't followed, its a bug and should panic.
Given these conventions, and assuming accessing custom-data from a tool is something that's done often - what would be a good way to simplify this expression?
Some possible options:
Remove the Option, just use Box<Any> with Box::new(()) representing None so access can be simplified a little.
Use a macro or function to hide verbosity - passing in the Option<Box<Any>>: will work of course, but prefer not - would use as a last resort.
Add a trait to Option<Box<Any>> which exposes a method such as tool.custom_data.unwrap_box::<StructOnlyForThisTool>() with matching unwrap_box_mut.
Update 1): since asking this question a point I didn't include seems relevant.
There may be multiple callback functions like execute which must all be able to access the custom_data. At the time I didn't think this was important to point out.
Update 2): Wrapping this in a function which takes tool isn't practical, since the borrow checker then prevents further access to members of tool until the cast variable goes out of scope, I found the only reliable way to do this was to write a macro.
If the implementation really only has a single method with a name like execute, that is a strong indication to consider using a closure to capture the implementation data. SomeTool can incorporate an arbitrary callable in a type-erased manner using a boxed FnMut, as shown in this answer. execute() then boils down to invoking the closure stored in the struct field implementation closure using (self.impl_)(). For a more general approach, that will also work when you have more methods on the implementation, read on.
An idiomatic and type-safe equivalent of the type+dataptr C pattern is to store the implementation type and pointer to data together as a trait object. The SomeTool struct can contain a single field, a boxed SomeToolImpl trait object, where the trait specifies tool-specific methods such as execute. This has the following characteristics:
You no longer need an explicit type field because the run-time type information is incorporated in the trait object.
Each tool's implementation of the trait methods can access its own data in a type-safe manner without casts or unwraps. This is because the trait object's vtable automatically invokes the correct function for the correct trait implementation, and it is a compile-time error to try to invoke a different one.
The "fat pointer" representation of the trait object has the same performance characteristics as the type+dataptr pair - for example, the size of SomeTool will be two pointers, and accessing the implementation data will still involve a single pointer dereference.
Here is an example implementation:
struct SomeTool {
impl_: Box<SomeToolImpl>,
}
impl SomeTool {
fn execute(&mut self) {
self.impl_.execute();
}
}
trait SomeToolImpl {
fn execute(&mut self);
}
struct SpecificTool1 {
foo_bar: bool
}
impl SpecificTool1 {
pub fn new(foo_bar: bool) -> SomeTool {
let my_data = SpecificTool1 { foo_bar: foo_bar };
SomeTool { impl_: Box::new(my_data) }
}
}
impl SomeToolImpl for SpecificTool1 {
fn execute(&mut self) {
println!("I am {}", self.foo_bar);
}
}
struct SpecificTool2 {
num: u64
}
impl SpecificTool2 {
pub fn new(num: u64) -> SomeTool {
let my_data = SpecificTool2 { num: num };
SomeTool { impl_: Box::new(my_data) }
}
}
impl SomeToolImpl for SpecificTool2 {
fn execute(&mut self) {
println!("I am {}", self.num);
}
}
pub fn main() {
let mut tool1: SomeTool = SpecificTool1::new(true);
let mut tool2: SomeTool = SpecificTool2::new(42);
tool1.execute();
tool2.execute();
}
Note that, in this design, it doesn't make sense to make implementation an Option because we always associate the tool type with the implementation. While it is perfectly valid to have an implementation without data, it must always have a type associated with it.

Aliasing trait inheritance with generics

I am starting to play with Rust for a new library. I'm trying to wrap my head around the possible ways to implement the following.
What follows is more of desired expression not real syntax. All of the ways I've tried to express this either don't compile, or don't compile when I go to implement one of the alias traits.
struct ConcreteType;
struct CommonType;
trait Handler<Rin, Rout = Rin>{
fn handle_event(&self, msg: &Rin);
}
// alias Handler with one of the types defined as a common case
trait HandlerToMessage<M> : Handler <ConcreteType, M>{
fn handle_event(&self, msg: &ConcreteType) {
// default implementation of parent trait
// example is simplified, forget about how Rout/M is actually used
self.decode(msg)
}
// method to implement
fn decode(&self, msg: &ConcreteType) -> M;
}
// another alias for most common case where Rin/Rout are ConcreteType, CommonType most often
trait HandlerToCommonType : HandlerToMessage <ConcreteType, CommonType>{
fn decode(&self, msg: &ConcreteType) -> CommonType
{
...
};
}
Alternative using associated types
trait Handler{
type Rin;
type Rout; // not yet able to do Rout = Rin with associated types
fn handle_event(&self, msg: &Self::Rin) -> Self::Rout;
}
trait HandlerToMessage : Handler <Rin=ConcreteType>{
fn handle_event(&self, msg: &Self::Rin) {
// common functionality
self.decode(msg)
}
// method to implement
fn decode(&self, msg: &Self::Rin) -> Self::Rout;
}
trait HandlerToCommonType : HandlerToMessage <Rout=CommonType>{
fn decode(&self, msg: &ConcreteType) -> CommonType
{
...
}
}
In C++ this is roughly what I want to accomplish
// real world example I've seen in the wild of this structure
template <class Rout>
class Context {
public:
void dispatch(Rout* msg);
};
template <class Rin, Rout = Rin>
class ReadHandler {
public:
void read (Context* ctx, Rin* msg) = 0;
private:
Context<Rout> ctx_;
};
// very common to convert from a byte buffer some message type
template <class M>
class BytesToMessageDecoder : ReadHandler<IOBuffer, M> {
public:
// Template method pattern
void read (Context* ctx, IOBuffer* msg) {
M msgOut;
bool success;
success = this->decode(msg, &msgOut);
if (success) {
ctx->dispatch(msgOut);
}
}
bool decode(IOBuffer* msg, M* msgOut) = 0;
}
// convert one byte buffer to another is common
typedef BytesToMessageDecoder<IOBuffer> BytesToBytesDecoder;
// Concrete implementations
// look for fixed number of bytes incoming
class FixedLengthFrameDecoder : BytesToBytesDecoder {
bool decode(IOBuffer* msg, IOBuffer* msgOut) { ... }
}
// fields are prefixed with a length. Wait for that many bytes and then dispatch
class LengthBasedFieldDecoder: BytesToBytesDecoder {
bool decode(IOBuffer* msg, IOBuffer* msgOut) { ... }
}
class StringDecoder : BytesToMessageDecoder<std::string> {
// decode from byte buffer to a string
bool decode(IOBuffer* msg, std::string* msgOut) { ... }
}
Basically the top level trait Handler is the most generic but maybe not meant to be implemented by anyone but advanced library users. The HandlerToMessage trait is meant to be a common conversion where we take ConcreteType and convert to some other type. The library may implement several of these. The HandlerToCommonType is the most common case that numerous library types would want to start from.
The details on how Rout is used in the Handler trait is not of importance. I tried to simplify the example and left off some arguments to hopefully make what I'm trying to convey more concise. All of my searching on this either has me thinking this isn't possible to convey or I am misusing it. I don't quite understand if this falls under the new specialization implementation, it doesn't feel like it from my understanding though.
I realize Rust is not C++ and so maybe what I'm trying to do is either not supported or has a different syntax. Any help is appreciated either in correct syntax or a more idiomatic Rust way.
Perhaps you can just have separate traits and implement one for all implementers of the other:
struct ConcreteType;
struct CommonType;
trait Handler<Input, Output = Input> {
fn handle_event(&self, msg: &Input) -> Output;
}
trait HandlerToMessage<M> {
fn decode(&self, msg: &ConcreteType) -> M;
}
impl<T, M> Handler<ConcreteType, M> for T
where T: HandlerToMessage<M>
{
fn handle_event(&self, msg: &ConcreteType) -> M {
self.decode(msg)
}
}
impl HandlerToMessage<CommonType> for () {
fn decode(&self, _msg: &ConcreteType) -> CommonType {
unimplemented!()
}
}
fn main() {}
The last one is really awkward because you'd normally implement a trait for a concrete type, but you haven't really presented any that make sense to implement for.

Resources