use pstd::collections::BTreeSet;
fn main() {
let s246 = BTreeSet::from([2, 4, 6]);
let s23456 = BTreeSet::from_iter(2..=6);
let mut iter = s246.difference(&s23456);
assert_eq!(iter.size_hint(), (0, Some(3)));
assert_eq!(iter.next(), None);
let s12345 = BTreeSet::from_iter(1..=5);
iter = s246.difference(&s12345);
assert_eq!(iter.size_hint(), (0, Some(3)));
assert_eq!(iter.next(), Some(&6));
assert_eq!(iter.size_hint(), (0, Some(0)));
assert_eq!(iter.next(), None);
}
It doesn't compile, I get an error:
error[E0597]: `s12345` does not live long enough
--> src/main.rs:11:28
|
10 | let s12345 = BTreeSet::from_iter(1..=5);
| ------ binding `s12345` declared here
11 | iter = s246.difference(&s12345);
| ^^^^^^^ borrowed value does not live long enough
...
16 | }
| -
| |
| `s12345` dropped here while still borrowed
| borrow might be used here, when `iter` is dropped and runs the destructor for type `pstd::collections::btree_set::Difference<'_, i32, CustomTuning>`
|
= note: values in a scope are dropped in the opposite order they are defined
For more information about this error, try `rustc --explain E0597`.
I vaguely understand the error, and if I change the line
iter = s246.difference(&s12345);
to
let mut iter = s246.difference(&s12345);
the error goes away and everything is fine. What I don't understand though is that if I use std rather than pstd ( the crate I am developing ) the error also goes away. Is there some magic to suppress the error in std? What is going on?
Could you post the definitions of your BTreeSet, your BTree Iterator and the difference function?
It's hard (or even impossible) to figure out lifetime issues without knowing the function signatures.
I am pretty sure it is because std uses the may_dangle attribute for the BTreeMap destructor.
The error message says something about the lifetime being dead when the object is dropped, which may_dangle suppresses. This is sadly one of the ways the std is currently a bit special.
The drop checker is i think one of the most subtle corners of Rust.
I don't really understand how it affects this code though. Has your Difference type Drop glue (a automatically generated drop function that only drops the fields)?
The std ones doesn't i think.
I don't know how that attribute works. There doesn't seem to be much documentation (makes sense, it's completely internal). At this point you might have more luck asking on the zulip: https://rust-lang.zulipchat.com/
I don't think so, there shouldn't be anything to do when dropping Difference. I agree that might be the crucial thing, whereas I don't think BTreeMap having a significant destructor should matter here. But I am a bit lost and not thinking too clearly!
I asked this because the error message says: when 'iter' is dropped and runs the destructor for type 'pstd::_::Difference<'_, i32>. So it seems like it has some kind of Drop glue. That would explain the difference.
I think I maybe figured it out. The iterator has a stack of values to keep track, implemented using arrayvec::ArrayVec. ArrayVec has a drop function. It doesn't actually do anything in this case because it will be full of references, but I guess the compiler won't be able to figure that out. The std BTreeMap doesn't need a stack because it stores parent pointers in the nodes.
Maybe I can make an ArrayVec for references only that doesn't have a drop function? Or is that thought nonsensical.
You could make a version of ArrayVec that doesn't implement Drop but also doesn't support types with drop glue (could be checked in a const block or you could require the items to implement Copy as a more limiting approximation).
// Check that T does not have a drop function.
const{ assert!(!std::mem::needs_drop::<T>(),"T has drop"); };
but then commented it out, as it is not exactly wrong to use it with a type that needs to be dropped, rather there is a risk of memory leaking if appropriate measures are not taken to empty the struct (which could typically be done with a drop function on the enclosing struct).
In other words, it seem unnecessarily restrictive.
It can be a pretty big footgun though! I would argue for keeping the assert by default and removing it only if you have a legit usecase that it prevents.
"
Returns true if dropping values of type T matters. This is purely an optimization hint, and may be implemented conservatively: it may return true for types that don’t actually need to be dropped.
"