0

How can I make a caching smart pointer in rust?

So that any equivalent objects can be handled as references of a single instance of that object, in the end I want to be able to take advantage of std::ptr::eq for equivalence-checking, instead of having to use PartialEq all the time.

I guess I want to be able to do something like this:

let object = String::from("hello");
let first = One::new(object);
let second = One::new(object);
assert!(std::ptr::eq(first, second));

Here's my attempt at making such a smart pointer

use std::collections::HashMap;
use std::hash::Hash;

trait OneKeyTrait: Hash + Sized {}

static mut cache: HashMap<dyn OneKeyTrait, One<dyn OneKeyTrait>> = HashMap::new();

pub struct One<T: OneKeyTrait> {
    count: usize,
    value: T,
}

impl<T: OneKeyTrait> One<T> {
    fn new(x: T) -> One<T> {
        if cache.contains_key(&x) {
            cache.get_mut(&x).count += 1;
            cache.get(&x)
        } else {
            let one_x = One {
                count: 1,
                value: x,
            };
            cache.insert(x, one_x);
            one_x
        }
    }
}

impl<T: OneKeyTrait> std::ops::Deref for One<T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        &self.value
    }
}

impl<T: OneKeyTrait> Drop for One<T> {
    fn drop(&mut self) {
        let mut one_x = cache.get_mut(&self.value);
        one_x.count -= 1;
        if one_x.count == 0 {
            cache.remove(&self.value);
        }
    }
}

fn main() {
    let object = String::from("hello");
    let first = One::new(object);
    let second = One::new(object);
    assert!(std::ptr::eq(first, second));
}

But it won't compile because

error[E0277]: the size for values of type `(dyn OneKeyTrait + 'static)` cannot be known at compilation time
 --> one.rs:6:19
  |
6 | static mut cache: HashMap<dyn OneKeyTrait, One<dyn OneKeyTrait>> = HashMap::new();
  |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
  |
  = help: the trait `Sized` is not implemented for `(dyn OneKeyTrait + 'static)`
note: required by a bound in `HashMap`

error[E0038]: the trait `OneKeyTrait` cannot be made into an object
 --> x.rs:6:48
  |
6 | static mut cache: HashMap<dyn OneKeyTrait, One<dyn OneKeyTrait>> = HashMap::new();
  |                                                ^^^^^^^^^^^^^^^ `OneKeyTrait` cannot be made into an object
  |
note: for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety>
 --> one.rs:4:27
  |
4 | trait OneKeyTrait: Hash + Sized {}
  |       -----------         ^^^^^ ...because it requires `Self: Sized`
  |       |
  |       this trait cannot be made into an object...
  = help: consider moving `hash` to another trait

theonlygusti
  • 11,032
  • 11
  • 64
  • 119
  • I suspect your ultimate problem here is the same as the one in [Trait implementing Sized](https://stackoverflow.com/q/31515921/364696) and [What does a trait requiring Sized have to do with being unable to have trait objects of that trait?](https://stackoverflow.com/q/53987976/364696). I'm not sure I fully understand it (relative Rust newbiw), but I think the basic problem is that: 1) Using `dyn OneKeyTrait` means you're making a "trait object", 2) "Trait objects" must be "object safe" (see link in your error message), 3) Being `Sized` definitionally means not object safe. – ShadowRanger Nov 29 '22 at 17:18
  • There are quite a few things you have to do to get this working, including working around `Hash` lacking object safety. What is the purpose of this code? It it supposed to be for interning? – PitaJ Nov 29 '22 at 17:30

1 Answers1

2

You've got two constraints:

  1. HashMaps need fixed size keys & values
  2. You need to use a trait object so your code works with any object meeting your trait requirements

With a major problem:

  1. Trait objects must made from a base trait that is object safe, but
  2. Sized traits are not object safe (for complicated reasons)

I believe the solution is to replace trait objects with simple Box wrappers; the wrapper is of fixed size, so HashMap can use it as a key/value, and the boxing allows you to invoke trait methods on the boxed objects.

Downside: You'll need to define a trait that includes eq, hash and as_any, then implement said trait for all relevant concrete types, to allow it to be used as a hash key.

ShadowRanger
  • 143,180
  • 12
  • 188
  • 271