1

In Java-speak, I am trying to create a collection (vector) of objects (strict instances), each one of which implements an interface (trait), so I can then iterate over the collection and call a method on all of them.

I have reduced it down to one sample file below which contains all the parts that I hope will make it easier to get answers.

// main.rs - try and compile using just "rustc main.rs"
use std::io::Result;

/// ////// Part 1
// Types used by implementors of the trait, and in creating a vector of implementors of the trai
pub struct SampleResult {
    metric: String,
}

pub trait SampleRunner {
    fn run(&self, &'static str) -> Result<SampleResult>;
}

pub struct Sample {
    name: &'static str,
    runner: &'static SampleRunner,
}

/// /////// Part 2
/// Create one specific static instance of such as Sample
static SAMPLE1: Sample = Sample {
    name: "sample",
    runner: &Sample1,
};

// need a struct to hold the run method to satisfy the trait???
struct Sample1;

// Implement the trait for this specific struct
impl SampleRunner for Sample1 {
    fn run(&self, name: &'static str) -> Result<SampleResult> {
        println!("Name: {}", name);
        Ok(SampleResult { metric: "OK".to_string() })
    }
}

/// /////// Part 3
/// Using the existing static instances of Sample struct, by creating a vector of references for them
/// then iterating over the vector and calling the trait method on each one
fn main() {
    let sample_set: Vec<&Sample> = vec![&SAMPLE1];

    for sample in sample_set.iter() {
        match sample.runner.run(sample.name) {
            Ok(result) => println!("Success"),
            _ => panic!("failed"),
        }
    }
}

That particular example fails with the message:

error[E0277]: the trait bound `SampleRunner + 'static: std::marker::Sync` is not satisfied in `Sample`
  --> <anon>:21:1
   |
21 |   static SAMPLE1: Sample = Sample {
   |  _^ starting here...
22 | |     name: "sample",
23 | |     runner: &Sample1,
24 | | };
   | |__^ ...ending here: within `Sample`, the trait `std::marker::Sync` is not implemented for `SampleRunner + 'static`
   |
   = note: `SampleRunner + 'static` cannot be shared between threads safely
   = note: required because it appears within the type `&'static SampleRunner + 'static`
   = note: required because it appears within the type `Sample`
   = note: shared static variables must have a type that implements `Sync`

But I have had many different problems depending on the approach I have taken, related to Sync, Sized, etc etc.

Lukas Kalbertodt
  • 79,749
  • 26
  • 255
  • 305
Andrew Mackenzie
  • 5,477
  • 5
  • 48
  • 70
  • For future visitors: (especially the title of) this question is very much related to [this question](http://stackoverflow.com/questions/25818082/vector-of-objects-belonging-to-a-trait). – Lukas Kalbertodt Apr 13 '17 at 17:46

1 Answers1

2

There are two little errors in the code. The first is described by the error, it tells us that the static value is not safe as it does not implement the Sync trait. It just tries to prepare for the case when the static value is manipulated from multiple threads. Here, the best solution is simply to mark the value as const. After that, there is some problem with the lifetime of &SAMPLE1 in main, can be solved by "using the let keyword to increase it's lifetime".

The code after these little modifications compiles, and looks like this:

use std::io::{Result};

pub struct SampleResult {
    metric: String
}

pub trait SampleRunner {
    fn run(&self, &'static str) -> Result<SampleResult>;
}

pub struct Sample {
    name: &'static str,
    runner: &'static SampleRunner
}

// Make it const
const SAMPLE1: Sample = Sample { name: "sample", runner: &Sample1 };

struct Sample1;

impl SampleRunner for Sample1 {
    fn run(&self, name: &'static str) -> Result<SampleResult> {
        println!("Name: {}", name);
        Ok(SampleResult {metric: "OK".to_string() })
    }
}

fn main() {
    // Extend the lifetime of the borrow by assigning it to a scope variable
    let borrowed_sample1 : &Sample = &SAMPLE1;
    let sample_set: Vec<&Sample> = vec!(borrowed_sample1);

    for sample in sample_set.iter() {
        match sample.runner.run(sample.name) {
        Ok(result) => println!("Success"),
        _ => panic!("failed")
        }
    }
}

However, I see that you are not satisfied with the code as you have to create a new struct for every implementation of SampleRunner. There is another way, using lambda functions (the Rust docs just refers to them as Closures).

use std::io::{Result};

pub struct SampleResult {
    metric: String
}

type SampleRunner = Fn(&'static str) -> Result<SampleResult>;

pub struct Sample {
    name: &'static str,
    runner: &'static SampleRunner
}

// Still const, use a lambda as runner    
const SAMPLE1: Sample = Sample { name: "sample", runner: &|name| {
  println!("Name: {}", name);
  Ok(SampleResult {metric: "OK".to_string() })
} };

fn main() {
    let borrowed_sample1 : &Sample = &SAMPLE1;
    let sample_set: Vec<&Sample> = vec!(borrowed_sample1);

    for sample in sample_set.iter() {
        // Must parenthese sample.runner so rust knows its a field not a method
        match (sample.runner)(sample.name) {
        Ok(result) => println!("Success"),
        _ => panic!("failed")
        }
    }
}
Tamas Hegedus
  • 28,755
  • 12
  • 63
  • 97
  • Thanks! I don't think I would *ever* have got to that from the error messages, or all the books I've read/Am reading. I've found them all a bit vague on the static vs. const thing, and I wouldn't have thought to use const for an initialized structure like that....as all the examples switch to static for non primitives (i32 etc). – Andrew Mackenzie Jul 09 '16 at 16:19
  • Any ideas on how I can make a "SampleRunner + 'static" able to be shared across threads? note: `SampleRunner + 'static` cannot be shared between threads safely. How to implement "Sync" on it? – Andrew Mackenzie Jul 10 '16 at 15:19
  • @AndrewMackenzie see [`RefCell`](https://doc.rust-lang.org/std/cell/struct.RefCell.html) – Tamas Hegedus Jul 11 '16 at 08:20
  • My goal was to avoid the need for any run-time like RefCell, as everything is static/const and read-only, so it should be sharable across threads as-is. – Andrew Mackenzie Jul 15 '16 at 14:46