user1244932
user1244932

Reputation: 8142

Is it possible to use a trait as syntax sugar for a free function?

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

Answers (1)

Matthieu M.
Matthieu M.

Reputation: 300159

There is a misunderstanding here:

  1. Rust has functions
  2. Rust has "functors"
  3. Rust has traits

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:

  • to provide bounds in generic functions
  • when object safe, to provide type erasure

Roughly speaking, a trait is not object safe if any of its associated functions:

  • does not have a &self or &mut self parameter
  • mentions Self (the type)

For more information on either concept, check the Rust Book.


In your situation, you could use:

  1. A simple pointer to function: fn(i32) -> String
  2. A functor Fn(i32) -> String
  3. An object safe trait

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

Related Questions