Rust create dynamic objects

#1

How to create an object and add or remove attributes in run time?

0 Likes

#2

It is not possible and counter-ideological in rust. You may create some HashMap<String, Any>, but it is probably very bad idea in such context.

1 Like

#3

This question is very vague, but maybe you’re looking for something like AnyMap? It’s meant to be used for things like dynamic configuration or context.

0 Likes

#4

@quadrupleslap Hi

I am looking for something like in C# similar to this. There is nothing vague in the question .

dynamic DynamicObject = new System.Dynamic.ExpandoObject();
DynamicObject.A = "A";
DynamicObject.B = "B";

DynamicObject.RunTimeMethod = new Func<int>(() => 
{ 
    return "Helo "; 
});
0 Likes

#5

For instance, will all the potential keys be known at compile-time? And what’s this for? There might be better alternatives to a dynamic object depending on your situation.

Edit: Here’s a short comparison of the two approaches listed here.

// cargo-deps: anymap

extern crate anymap;

use anymap::AnyMap;
use std::any::Any;
use std::collections::HashMap;

fn main() {
    option1();
    option2();
}

// Option 1 - HashMap<String, Box<Any>>
//
// - This is fully dynamic and therefore a massive PITA.
// - You can use &'static str if all the keys are known at compile-time, but
//   otherwise you'll need to use String.

fn option1() {
    let mut dynamic_object = HashMap::<&'static str, Box<Any>>::new();

    dynamic_object.insert("A", Box::new("A".to_string()));
    dynamic_object.insert("B", Box::new("B".to_string()));
    // If you forget this cast, the code will compile but the closure will have a private type!
    dynamic_object.insert("RuntimeMethod", Box::new(Box::new(|_| "Hello".into()) as Box<Fn(i32) -> String>));

    println!("A = {:?}", dynamic_object["A"].downcast_ref::<String>().unwrap());
    println!("RuntimeMethod(1) = {:?}", dynamic_object["RuntimeMethod"].downcast_ref::<Box<Fn(i32) -> String>>().unwrap()(1));
}

// Option 2 - AnyMap
//
// - You need a type for each key, so all keys need to be known at compile-time.
// - This is a lot easier to use than the HashMap, and a lot harder to get wrong.
// - It's also marginally faster, since it uses TypeIDs (64-bit integers) instead of strings, and a custom hasher.

pub struct A(String);
pub struct B(String);
pub struct RuntimeMethod(Box<Fn(i32) -> String>);

fn option2() {
    let mut dynamic_object = AnyMap::new();

    dynamic_object.insert(A("A".into()));
    dynamic_object.insert(B("B".into()));
    dynamic_object.insert(RuntimeMethod(Box::new(|_| "Hello".into())));

    println!("A = {:?}", dynamic_object.get::<A>().unwrap().0);
    println!("RuntimeMethod(1) = {:?}", dynamic_object.get::<RuntimeMethod>().unwrap().0(1));
}
0 Likes

#6

@quadrupleslap Potential keys are unknown. The purpose is to have a mock object for testing . An object where I can mutate during run time .

0 Likes

#7

For testing better option is just to orient your codebase around traits, and implement those you want to mock as mock objects. Some time ago I was using galvanic mock, I am not sure how is it following new rust features.

3 Likes

#8

In Rust, the compiler must know the type of everything at compile time. What is dynamic_object.A? Since you’re modifying it at runtime, the compiler cannot know this.

People are suggesting making the type be Box<Any>, which is one option, however if you do this, you will need to call downcast_ref::<T>().unwrap() where T is the type you stored in the box. This is probably not what you’re looking for.

I’m assuming what you want to replace some struct in a test with a mock of some sort. If the code you’re testing asks for an enum or struct, sorry it’s not possible. If the code asks for a trait, create your own mock struct and implement the trait for your mock struct.

Edit: To clarify, I assume in your mock test you’re going to do something conceptually similar to defining your own database connection object that doesn’t actually connect to a database, and then passing the mock database to the functions you’re testing. In this case in order to be able to mock the database, the function must be generic over the database, and you should create a new mock struct that implements your database trait.

If it isn’t generic over types of databases, then you can only pass in the type it accepts. Either you will need to add the functionality directly in the database, or you will have to change the function to be generic over a trait.

2 Likes