Reputation: 8142
I want to call function depending on external data, like this:
struct Foo {
data: &'static str,
handler: Option<fn (i32) -> String>,
}
fn aaa_converter(_: i32) -> String { unimplemented!(); }
fn bbb_converter(_: i32) -> String { unimplemented!(); }
fn main() {
let _ = Foo{data: "aaa", handler: Some(aaa_converter)};
let _ = Foo{data: "bbb", handler: Some(bbb_converter)};
let _ = Foo{data: "ccc", handler: None};
}
I have as input the string "aaa", and I need to call aaa_converter
. All works fine, I put Foo
objects into the hash map and call proper handler
if not None
.
Now I have many such converters and I want to help from the language to deal with them.
Ideally, there would be syntax like this:
trait Handler {
fn handle(a: i32) -> String;
}
impl Handler for "aaa" {
// ...
}
The best match that I can have is:
trait Handler {
fn handle(/*&self, */a: i32) -> String;
}
struct aaa;
impl Handler for aaa {
fn handle(/*&self, */a: i32) -> String {
unimplemented!();
}
}
struct Foo {
data: &'static str,
handler: &'static Handler,
}
fn main() {}
But such code does not compile:
the trait `Handler` cannot be made into an object
= note: method `handle` has no receiver
How to call a trait method without a struct instance? looks related, but the RFC linked in the answer is out of date. It's also possible something changed since then in the language?
Is it possible to use a trait as a simple pointer to a free function?
Or is there another way to organize handlers?
Upvotes: 1
Views: 593
Reputation: 300159
There is a misunderstanding here:
A function is just that:
fn i_am_a_function(a: i32) -> String { a.to_string() }
A functor is a function object, that is a function associated to some state. Rust actually has 3 of them:
FnOnce(i32) -> String
FnMut(i32) -> String
Fn(i32) -> String
And finally Rust has traits:
trait Handler {
fn non_object_safe(a: i32) -> String;
fn object_safe(&self, a: i32) -> String;
}
Traits can be used in two situations:
Roughly speaking, a trait is not object safe if any of its associated functions:
&self
or &mut self
parameterSelf
(the type) For more information on either concept, check the Rust Book.
In your situation, you could use:
fn(i32) -> String
Fn(i32) -> String
The only thing you cannot use is a non object-safe trait, and of course thanks to Murphy, it's the one option you picked.
In your case, the simplest solution is to use an object safe trait:
trait Handler {
fn handle(&self, a: i32) -> String;
}
struct A;
impl Handler for A {
fn handle(&self, a: i32) -> String {
a.to_string()
}
}
const STATIC_A: &'static Handler = &A;
struct Foo {
data: &'static str,
handler: &'static Handler,
}
fn main() {
let foo = Foo { data: "aaa", handler: STATIC_A };
println!("{}", foo.handler.handle(3));
}
If that 64-bits overhead for the data-pointer really bother you, then you can use function pointers and build your own virtual table:
struct Handler {
handle: fn(i32) -> String,
}
fn aaa(a: i32) -> String {
a.to_string()
}
const STATIC_A: &'static Handler = &Handler { handle: aaa };
struct Foo {
data: &'static str,
handler: &'static Handler,
}
fn main() {
let foo = Foo { data: "aaa", handler: STATIC_A };
println!("{}", (foo.handler.handle)(3));
}
It's less ergonomic, but it's also 64-bits smaller!
Upvotes: 1