What I want to know is if it's possible to implement some trait so that when a function wants a &'a Owning, I can instead give it a NonOwning<'a>, without having to clone anything. Typically, I have a HashSet<Owning>, and I want to check if it contains a NonOwning.
You can’t solve this with the approach you’ve presented here, because you need to actually have an Owning stored somewhere for the reference to point to.
There are, however , some tricks you can pull with Borrow<dyn …> to deal with the hash set. When I get to a real computer, I’ll try to give an example.
This has the costs that you have to write Data<'static> for owning instances, and each usage of others will result in some run-time checking for which kind it is.
You can make first be the first element of others instead of a separate field, so that this is itself an ordinary case of owning or borrowing a slice. If you do this, you can still provide your desired structure by making an unsized wrapper on the slice itself:
The fundamental idea here is that Borrow represents cross-type comparisons. Your two types have different data layouts, but that's exactly what trait objects are designed to abstract over. Implementing this is pretty straightforward, but a bit longwinded:
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct NonOwning<'a> {
first: usize,
others: &'a [usize],
}
#[derive(Debug, Clone)]
pub struct Owning {
first: usize,
others: Vec<usize>,
}
// Trait to define the comparison
pub trait NonOwningView {
fn non_owning(&self)->NonOwning<'_>;
}
impl NonOwningView for NonOwning<'_> {
fn non_owning(&self)->NonOwning<'_> { *self }
}
impl NonOwningView for Owning {
fn non_owning(&self)->NonOwning<'_> {
NonOwning { first: self.first, others: &self.others[..] }
}
}
// How to compare trait objects
impl std::cmp::PartialEq for dyn NonOwningView + '_ {
fn eq(&self, other: &Self)->bool {
self.non_owning() == other.non_owning()
}
}
impl std::cmp::Eq for dyn NonOwningView + '_ {}
impl std::hash::Hash for dyn NonOwningView + '_ {
fn hash<H: std::hash::Hasher>(&self, hasher:&mut H) {
self.non_owning().hash(hasher)
}
}
// Make sure Owned compares the same as the trait objects do
impl std::cmp::PartialEq for Owning {
fn eq(&self, other: &Self)->bool {
(self as &dyn NonOwningView) == (other as &dyn NonOwningView)
}
}
impl std::cmp::Eq for Owning {}
impl std::hash::Hash for Owning {
fn hash<H: std::hash::Hasher>(&self, hasher:&mut H) {
(self as &dyn NonOwningView).hash(hasher)
}
}
// Tell HashSet, etc. that it can use trait object for comparison
use std::borrow::Borrow;
impl<'a> Borrow<dyn NonOwningView + 'a> for Owning {
fn borrow(&self)->&(dyn NonOwningView + 'a) { self }
}
impl<'a, 'b:'a> Borrow<dyn NonOwningView + 'a> for NonOwning<'b> {
fn borrow(&self)->&(dyn NonOwningView + 'a) { self }
}
// Example use
#[test]
fn test_borrow() {
use std::collections::HashSet;
let set = [
Owning { first: 3, others: vec![5,7]},
].into_iter().collect::<HashSet<_>>();
assert!(set.contains::<dyn NonOwningView>(&NonOwning { first: 3, others: &[5,7]}))
}
Hey, thank you both for your replies.
I'll investigate further to see which one I'm going to use, but honestly, given the potential size of my data, I don't think the neatness of your solutions is going to be worth the added complexity, compared to just discarding NonOwning and just cloning Owning when necessary.
In any case, it was very instructive. Thank you very much.