How to implement Copy for BTreeMap?

Hi, guys, I met a problem. This is my codes:

#[derive(Copy, Clone)]
struct Filter {
    rule: &'static str,
    callback: &'static dyn Fn(&Request<Body>) -> ResponseResult,
}

#[derive(Copy, Clone)]
pub struct Router {
    table: BTreeMap<&'static str, Filter>,
}

impl Router {
    pub fn new() -> Router {
        Router {
            table: BTreeMap::new(),
        }
    }

    pub fn add_rule(&mut self, rule: &'static str, callback: &'static dyn Fn(&Request<Body>) -> ResponseResult) {
        let filter = Filter {
            rule: rule,
            callback: callback,
        };

        self.table.insert(rule, filter);
    }

    pub fn route(&self, rule: &'static str) -> Option<&'static dyn Fn(&Request<Body>) -> ResponseResult> {
        if let Some(filter) = self.table.get(rule) {
            Some(filter.callback)
        } else {
            None
        }
    }
}

unsafe impl Send for Filter {}

unsafe impl Sync for Filter {}

unsafe impl Send for Router {}

unsafe impl Sync for Router {}

I use my Router struct in multiple threads context. But the compiler told me these:

error[E0204]: the trait `Copy` may not be implemented for this type
  --> src/router.rs:20:10
   |
20 | #[derive(Copy, Clone)]
   |          ^^^^
21 | pub struct Router {
22 |     table: BTreeMap<&'static str, Filter>,
   |     ------------------------------------- this field does not implement `Copy`
   |
   = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)

If I use BTreeMap in multiple threads context, what should I do?

There's a lot going on here, but here's a few comments. You can't implement copy there. Nor do you want to. This is from the rust docs. The same argument that applies to String applies to BTreeMap

Copies happen implicitly, for example as part of an assignment y = x . The behavior of Copy is not overloadable; it is always a simple bit-wise copy.

Cloning is an explicit action, x.clone() . The implementation of Clone can provide any type-specific behavior necessary to duplicate values safely. For example, the implementation of Clone for String needs to copy the pointed-to string buffer in the heap. A simple bitwise copy of String values would merely copy the pointer, leading to a double free down the line. For this reason, String is Clone but not Copy .

Setting aside copy, what you're trying to do is share your router across threads and you have not done anything to guarantee thread safety. There is a reason it is unsafe to impl Sync and that's because you need to guarantee that two threads can't make changes to Router on two different thread at the same time. I would definitely delete the unsafe impl's. You should be very confident you are right before reaching for unsafe. The way around that is to change table to be type Arc<Mutex<BTreeMap<..>>>. By doing that your type will automatically be Send and Sync because that type is Send and Sync so you don't need to implement it with unsafe. It requires a little bit more work to use said type, but I would just try it out and see where you get stuck.

With that, to get Filter to work with Send/Sync, you need to restrict the callback to be Send + Sync as well, which looks like this

#[derive(Clone)]
struct Filter {
    rule: &'static str,
    callback: &'static (dyn Fn(&Request<Body>) -> ResponseResult  + Send + Sync),
}

These should work based on the code shown, but I wouldn't be surprised if you run into other issues since getting multi threaded code working is tricky. In Rust in particular, it can be hard to get it to compile, but the flip side of that is if you get it to compile (without unsafe), you can be reasonably confident it won't crash.

Thanks, I've recognized the risks of sharing data. I think maybe I need to try another solution.

BTW, if you don't intend to make this usable only with string literals, and prevent normal strings from working, then &'static str is the wrong type to use.

In normal Rust code use of references inside structs is rare, and 'static is almost never used. You should prefer using owning types in structs, like Box<str> or String.

Even if you think string literals are a useful optimization, a more flexible approach would be to use Cow<'static, str> for the strings (it allows use of normal strings without leaking memory).

Copy is only for types that are so small and simple, that it won't hurt if they're copied by accident. It's OK for a type like Point, but a wrong thing (even if it worked and didn't cause crashes) for large, complex mutable types like BTreeMap.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.