Using a Rust macro to generate a function with variable parameters - rust

I've created a Rust macro which expands to a function declaration.
macro_rules! build_fn
{
($name:tt) => {
pub fn $name(&self) -> Result<i32, Box<dyn Error>>
{
// <implementation>
Ok(0)
}
};
}
Is it possible to expand this so the macro can take variable parameters?
e.g.
($name:tt, /*$variable_args ? */) => {
pub fn $name(&self, /*$variable_args ? */) -> Result<i32, Box<dyn Error>>
{
// ...
Ok(0)
}
};
}
Playground

Indeed, it's possible. You need to expand the parameters as $field: $ty:
use std::error::Error;
macro_rules! build_fn
{
($name:tt, $($v:ident: $t:ty),*) => {
pub fn $name(&self, $($v: $t),*)
{
let args = [$($v,)*];
println!("Args = {:?}", args);
}
};
}
struct MyStruct {}
impl MyStruct {
build_fn!(test_single_arg, x: i32);
build_fn!(test_multi_arg, x: i32, y: i32);
}
fn main() -> Result<(), Box<dyn Error>> {
let my_struct = MyStruct {};
my_struct.test_single_arg(10);
my_struct.test_multi_arg(1, 2);
Ok(())
}
Link to the playground: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=d798cc6a744b53af47dd8cf40fc3b115

Since #ranfdev's answer is not currently compiling, I've fixed & simpified & extended it to support multiple types -- and it now runs in Rust stable:
use std::fmt::Write;
macro_rules! build_vararg_fn {
($name:tt, $($v:tt: $t:ty),+) => {
fn $name($($v: $t),+) {
let mut msg = String::from("args: ");
$(
write!(msg, "{:?}, ", $v).unwrap();
)+
println!("{}", &msg[..msg.len()-2]);
}
}
}
fn main() {
build_vararg_fn!(test_single_arg, x: i32);
build_vararg_fn!(test_multi_arg, x: i32, y: i32);
build_vararg_fn!(test_multi_type, x: i32, y: i32, z: f64);
test_single_arg(10);
test_multi_arg(1, 2);
test_multi_type(1, 2, 3.14159);
}
Output:
args: 10
args: 1, 2
args: 1, 2, 3.14159
See it on Playgroud!

Related

Heterogeneous collection as a member of a class in Rust

I am new to Rust, and does not fully understand lifetime, so probably, that is why I can't solv the following issue. I need a solution in which a class has a heterogeneous HashMap containing different objects derived from the same trait.
I have to be able to extend an object with some (multiple) functionality dinamically. Other solutions are also welcome. Adding functionality to the class in compile time could also work, but adding functionality directly to the main class not.
use std::collections::HashMap;
trait DoerTrait {
fn do_something( & self, a : u8, b : u8 ) -> u8;
}
struct MyDoer<'a> {
}
impl DoerTrait for MyDoer<'a> {
fn do_something( & self, a : u8, b : u8 ) -> u8 {
return a + b;
}
}
struct MyMain<'a> {
doers : HashMap<u8,&'a dyn DoerTrait>,
}
impl<'a> MyMain<'a> {
fn new() -> Self {
Self {
doers : HashMap::new()
}
}
fn add_doer( &mut self, id : u8, doer : & dyn DoerTrait ) {
self.doers.insert( id, doer );
}
fn do_something( & self, id : u8 ) {
match self.doers.get( &id ) {
Some( doer ) => {
println!( "{}", doer(19,26) );
}
None => {
println!( "Doer not specified!" );
}
}
}
}
fn main() {
let mut mymain = MyMain::new();
let mydoer = MyDoer{};
mymain.add_doer( 42, &mydoer );
mymain.do_something( 42 );
}
Not too sure what issue you have, once MyDoer has been stripped of its incorrect (unnecessary) lifetime and the lifetime has correctly been declared on impl MyMain, the compiler directly points to the parameter of add_doer not matching (after which it points out that doer in do_something is not a function):
use std::collections::HashMap;
trait DoerTrait {
fn do_something(&self, a: u8, b: u8) -> u8;
}
struct MyDoer;
impl DoerTrait for MyDoer {
fn do_something(&self, a: u8, b: u8) -> u8 {
return a + b;
}
}
struct MyMain<'a> {
doers: HashMap<u8, &'a dyn DoerTrait>,
}
impl<'a> MyMain<'a> {
fn new() -> Self {
Self {
doers: HashMap::new(),
}
}
fn add_doer(&mut self, id: u8, doer: &'a dyn DoerTrait) {
self.doers.insert(id, doer);
}
fn do_something(&self, id: u8) {
match self.doers.get(&id) {
Some(doer) => {
println!("{}", doer.do_something(19, 26));
}
None => {
println!("Doer not specified!");
}
}
}
}
fn main() {
let mut mymain = MyMain::new();
let mydoer = MyDoer {};
mymain.add_doer(42, &mydoer);
mymain.do_something(42);
}

Optional trait's method: call empty method or skip it at all?

If I have an optional method that has to be called many many times what is better if I want to skip it: have an empty body and call it or check the bool/Option before calling it?
The following benchmark make no sense. It gave zeroes.
#![feature(test)]
extern crate test;
trait OptTrait: 'static {
fn cheap_call(&mut self, data: u8);
fn expensive_call(&mut self, data: u8);
}
type ExpensiveFnOf<T> = &'static dyn Fn(&mut T, u8);
struct Container<T: OptTrait> {
inner: T,
expensive_fn: Option<ExpensiveFnOf<T>>,
}
impl<T: OptTrait> Container<T> {
fn new(inner: T, expensive: bool) -> Self {
let expensive_fn = {
if expensive {
Some(&T::expensive_call as ExpensiveFnOf<T>)
} else {
None
}
};
Self {
inner,
expensive_fn,
}
}
}
struct MyStruct;
impl OptTrait for MyStruct {
fn cheap_call(&mut self, _data: u8) {
}
fn expensive_call(&mut self, _data: u8) {
}
}
#[cfg(test)]
mod tests {
use super::*;
use test::Bencher;
#[bench]
fn bench_always_call_empty(b: &mut Bencher) {
let mut cont = Container::new(MyStruct, false);
b.iter(|| {
cont.inner.cheap_call(0);
cont.inner.expensive_call(1);
});
}
#[bench]
fn bench_alwaws_skip_empty(b: &mut Bencher) {
let mut cont = Container::new(MyStruct, false);
b.iter(|| {
cont.inner.cheap_call(0);
if let Some(func) = cont.expensive_fn {
func(&mut cont.inner, 1);
}
});
}
}

How to create a single threaded singleton in Rust?

I'm currently trying to wrap a C library in rust that has a few requirements. The C library can only be run on a single thread, and can only be initialized / cleaned up once on the same thread. I want something something like the following.
extern "C" {
fn init_lib() -> *mut c_void;
fn cleanup_lib(ctx: *mut c_void);
}
// This line doesn't work.
static mut CTX: Option<(ThreadId, Rc<Context>)> = None;
struct Context(*mut c_void);
impl Context {
fn acquire() -> Result<Rc<Context>, Error> {
// If CTX has a reference on the current thread, clone and return it.
// Otherwise initialize the library and set CTX.
}
}
impl Drop for Context {
fn drop(&mut self) {
unsafe { cleanup_lib(self.0); }
}
}
Anyone have a good way to achieve something like this? Every solution I try to come up with involves creating a Mutex / Arc and making the Context type Send and Sync which I don't want as I want it to remain single threaded.
A working solution I came up with was to just implement the reference counting myself, removing the need for Rc entirely.
#![feature(once_cell)]
use std::{error::Error, ffi::c_void, fmt, lazy::SyncLazy, sync::Mutex, thread::ThreadId};
extern "C" {
fn init_lib() -> *mut c_void;
fn cleanup_lib(ctx: *mut c_void);
}
#[derive(Debug)]
pub enum ContextError {
InitOnOtherThread,
}
impl fmt::Display for ContextError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
ContextError::InitOnOtherThread => {
write!(f, "Context already initialized on a different thread")
}
}
}
}
impl Error for ContextError {}
struct StaticPtr(*mut c_void);
unsafe impl Send for StaticPtr {}
static CTX: SyncLazy<Mutex<Option<(ThreadId, usize, StaticPtr)>>> =
SyncLazy::new(|| Mutex::new(None));
pub struct Context(*mut c_void);
impl Context {
pub fn acquire() -> Result<Context, ContextError> {
let mut ctx = CTX.lock().unwrap();
if let Some((id, ref_count, ptr)) = ctx.as_mut() {
if *id == std::thread::current().id() {
*ref_count += 1;
return Ok(Context(ptr.0));
}
Err(ContextError::InitOnOtherThread)
} else {
let ptr = unsafe { init_lib() };
*ctx = Some((std::thread::current().id(), 1, StaticPtr(ptr)));
Ok(Context(ptr))
}
}
}
impl Drop for Context {
fn drop(&mut self) {
let mut ctx = CTX.lock().unwrap();
let (_, ref_count, ptr) = ctx.as_mut().unwrap();
*ref_count -= 1;
if *ref_count == 0 {
unsafe {
cleanup_lib(ptr.0);
}
*ctx = None;
}
}
}
I think the most 'rustic' way to do this is with std::sync::mpsc::sync_channel and an enum describing library operations.
The only public-facing elements of this module are launch_lib(), the SafeLibRef struct (but not its internals), and the pub fn that are part of the impl SafeLibRef.
Also, this example strongly represents the philosophy that the best way to deal with global state is to not have any.
I have played fast and loose with the Result::unwrap() calls. It would be more responsible to handle error conditions better.
use std::sync::{ atomic::{ AtomicBool, Ordering }, mpsc::{ SyncSender, Receiver, sync_channel } };
use std::ffi::c_void;
extern "C" {
fn init_lib() -> *mut c_void;
fn do_op_1(ctx: *mut c_void, a: u16, b: u32, c: u64) -> f64;
fn do_op_2(ctx: *mut c_void, a: f64) -> bool;
fn cleanup_lib(ctx: *mut c_void);
}
enum LibOperation {
Op1(u16,u32,u64,SyncSender<f64>),
Op2(f64, SyncSender<bool>),
Terminate(SyncSender<()>),
}
#[derive(Clone)]
pub struct SafeLibRef(SyncSender<LibOperation>);
fn lib_thread(rx: Receiver<LibOperation>) {
static LIB_INITIALIZED: AtomicBool = AtomicBool::new(false);
if LIB_INITIALIZED.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst).is_err() {
panic!("Tried to double-initialize library!");
}
let libptr = unsafe { init_lib() };
loop {
let op = rx.recv();
if op.is_err() {
unsafe { cleanup_lib(libptr) };
break;
}
match op.unwrap() {
LibOperation::Op1(a,b,c,tx_res) => {
let res: f64 = unsafe { do_op_1(libptr, a, b, c) };
tx_res.send(res).unwrap();
},
LibOperation::Op2(a, tx_res) => {
let res: bool = unsafe { do_op_2(libptr, a) };
tx_res.send(res).unwrap();
}
LibOperation::Terminate(tx_res) => {
unsafe { cleanup_lib(libptr) };
tx_res.send(()).unwrap();
break;
}
}
}
}
/// This needs to be called no more than once.
/// The resulting SafeLibRef can be cloned and passed around.
pub fn launch_lib() -> SafeLibRef {
let (tx,rx) = sync_channel(0);
std::thread::spawn(|| lib_thread(rx));
SafeLibRef(tx)
}
// This is the interface that most of your code will use
impl SafeLibRef {
pub fn op_1(&self, a: u16, b: u32, c: u64) -> f64 {
let (res_tx, res_rx) = sync_channel(1);
self.0.send(LibOperation::Op1(a, b, c, res_tx)).unwrap();
res_rx.recv().unwrap()
}
pub fn op_2(&self, a: f64) -> bool {
let (res_tx, res_rx) = sync_channel(1);
self.0.send(LibOperation::Op2(a, res_tx)).unwrap();
res_rx.recv().unwrap()
}
pub fn terminate(&self) {
let (res_tx, res_rx) = sync_channel(1);
self.0.send(LibOperation::Terminate(res_tx)).unwrap();
res_rx.recv().unwrap();
}
}

Why can I not dereference or add a generic type even though I'm passing in a type that can be dereferenced and added?

I have some problems with a generic implementation of a method:
use std::collections::BTreeMap;
use global::entity::EntityId;
struct simple_system<T> {
list_sum: BTreeMap<EntityId, T>,
list_com: BTreeMap<EntityId, Vec<T>>,
}
impl<T> simple_system<T> {
pub fn new() -> simple_system<T> {
simple_system {
list_sum: BTreeMap::new(),
list_com: BTreeMap::new(),
}
}
pub fn addComponent(&mut self, id: EntityId, comp: T) {
self.list_com.entry(id).or_insert_with(Vec::new).push(comp);
match self.list_sum.get_mut(&id) {
Some(v) => *v = *v + *comp,
None => self.list_sum.insert(id, comp),
}
}
}
with the following errors.
error[E0614]: type `T` cannot be dereferenced
--> src/main.rs:20:34
|
20 | Some(v) => *v = *v + *comp,
| ^^^^^
error[E0369]: binary operation `+` cannot be applied to type `T`
--> src/main.rs:20:29
|
20 | Some(v) => *v = *v + *comp,
| ^^^^^^^^^^
|
= note: an implementation of `std::ops::Add` might be missing for `T`
I don't know what I have to change to get it to work. I use it with u32 type so it should have an + operator.
The Rust generics systems doesn't work the way C++ templates do: in C++ the compiler doesn't check whether the code actually compiles with any type in advance.
Rust makes sure the function compiles with any type that fulfills the listed requirements (called trait bounds). The compiler already told you what is missing: std::ops::Add might be missing for T, so ask for it:
impl<T: Add<Output = T>> simple_system<T> { /* … */ }
This will not fix everything; your code has other issues as well.
And here the Solution:
At first you should write a working non generic (c++ template) version and then evolve it to a generic version.
use std::collections::BTreeMap;
#[derive(Debug)]
struct SumUpSystem {
list_sum: BTreeMap<u64, i32 >,
list_com: BTreeMap<u64, Vec<i32> >
}
impl SumUpSystem {
pub fn new() -> SumUpSystem {
SumUpSystem {
list_sum: BTreeMap::new(),
list_com: BTreeMap::new()
}
}
pub fn add_component(&mut self, id: u64, comp: i32) {
self.list_com.entry(id).or_insert_with(Vec::new).push(comp);
let mut insert = false;
match self.list_sum.get_mut(&id) {
Some(x) => *x = *x + comp,
None => insert = true
}
if (insert) {
self.list_sum.insert(id, comp);
}
}
pub fn sum(& self, id: u64) -> i32 {
if let Some(x) = self.list_sum.get(&id) {
*x
} else {
panic!("Invalid id: Not in system!");
}
}
}
#[cfg(test)]
mod test {
use super::*;
macro_rules! init_test {
($v:ident) => (let mut $v : SumUpSystem = SumUpSystem::new(););
}
#[test]
fn add_component() {
init_test!(s);
s.add_component(1, 13);
assert_eq!(s.sum(1), 13);
s.add_component(1, 26);
assert_eq!(s.sum(1), 13 + 26);
}
}
The generic (c++ template). You should read the Trait section of the Rust Documentation to understand how/why it works.
use std::collections::BTreeMap;
use std::ops::Add;
#[derive(Debug)]
struct SumUpSystem<T> {
list_sum: BTreeMap<u64, T >,
list_com: BTreeMap<u64, Vec<T> >
}
impl <T: Add<Output=T> + Clone> SumUpSystem<T> {
pub fn new() -> SumUpSystem<T> {
SumUpSystem {
list_sum: BTreeMap::new(),
list_com: BTreeMap::new()
}
}
pub fn add_component(&mut self, id: u64, comp: &T) {
self.list_com.entry(id).or_insert_with(Vec::new).push(comp.clone());
let mut insert = false;
match self.list_sum.get_mut(&id) {
Some(x) => *x = x.clone() + comp.clone(),
None => insert = true
}
if insert {
self.list_sum.insert(id, comp.clone());
}
}
pub fn sum(& self, id: u64) -> T {
if let Some(x) = self.list_sum.get(&id) {
x.clone()
} else {
panic!("Invalid id: Not in system!");
}
}
}
#[cfg(test)]
mod test {
use super::*;
macro_rules! init_test {
($v:ident) => (let mut $v : SumUpSystem<i32> = SumUpSystem::new(););
}
#[test]
fn add_component() {
init_test!(s);
s.add_component(1, &13i32);
assert_eq!(s.sum(1), 13i32);
s.add_component(1, &26i32);
assert_eq!(s.sum(1), 39i32);
}
}

Is there a way to perform an index access to an instance of a struct?

Is there a way to perform an index access to an instance of a struct like this:
struct MyStruct {
// ...
}
impl MyStruct {
// ...
}
fn main() {
let s = MyStruct::new();
s["something"] = 533; // This is what I need
}
You can use the Index and IndexMut traits.
use std::ops::{Index, IndexMut};
struct Foo {
x: i32,
y: i32,
}
impl Index<&'_ str> for Foo {
type Output = i32;
fn index(&self, s: &str) -> &i32 {
match s {
"x" => &self.x,
"y" => &self.y,
_ => panic!("unknown field: {}", s),
}
}
}
impl IndexMut<&'_ str> for Foo {
fn index_mut(&mut self, s: &str) -> &mut i32 {
match s {
"x" => &mut self.x,
"y" => &mut self.y,
_ => panic!("unknown field: {}", s),
}
}
}
fn main() {
let mut foo = Foo { x: 0, y: 0 };
foo["y"] += 2;
println!("x: {}", foo["x"]);
println!("y: {}", foo["y"]);
}
It prints:
x: 0
y: 2
You want to use the Index trait (and its pair IndexMut):
use std::ops::Index;
#[derive(Copy, Clone)]
struct Foo;
struct Bar;
impl Index<Bar> for Foo {
type Output = Foo;
fn index<'a>(&'a self, _index: Bar) -> &'a Foo {
println!("Indexing!");
self
}
}
fn main() {
Foo[Bar];
}

Resources