I have a static array type that allows you to create multiple read only "view" slices into the data it holds; but on Drop
it assert!
s that there are no "hanging" views that reference data that no longer exists.
It seems like you could do this by adding a heap-allocated integer to the structure, like in the unsafe guide; something like:
extern crate libc;
use libc::{c_void, calloc, free, size_t};
use std::mem::size_of;
struct Foo {
count: *mut i32,
value: i32,
}
impl Foo {
fn new(parent: Option<&mut Foo>) -> Foo {
match parent {
Some(p) => {
unsafe {
let tmp = &mut *p.count;
*tmp += 1;
println!("Created a new record, the count is now: {}", *tmp);
}
return Foo {
value: 0,
count: p.count,
};
}
None => unsafe {
let counter = calloc(size_of::<i32> as size_t, 1 as size_t) as *mut i32;
println!("counter record: {}", *counter);
return Foo {
value: 0,
count: counter,
};
},
}
}
fn count(&self) -> i32 {
unsafe {
return *self.count;
}
}
}
Where the drop
implementation updates the counter:
impl Drop for Foo {
fn drop(&mut self) {
unsafe {
let tmp = &mut *self.count;
*tmp -= 1;
println!("Dropped a record, the count is now: {}", *tmp);
if *tmp == -1 {
println!("counter record: {}", *self.count);
free(self.count as *mut c_void);
println!("The final record was dropped");
}
}
}
}
This code works fine, the test:
fn main() {
let mut parent = Foo::new(None);
{
let child1: Foo;
let child2: Foo;
let child3: Foo;
let child4: Foo;
let child5: Foo;
{ child1 = Foo::new(Some(&mut parent)); }
{ child2 = Foo::new(Some(&mut parent)); }
{ child3 = Foo::new(Some(&mut parent)); }
{ child4 = Foo::new(Some(&mut parent)); }
{ child5 = Foo::new(Some(&mut parent)); }
assert!(parent.count() == 5);
}
assert!(parent.count() == 0);
}
Yields:
counter record: 0x7f909f7fc010
Created a new record, the count is now: 1
Created a new record, the count is now: 2
Created a new record, the count is now: 3
Created a new record, the count is now: 4
Created a new record, the count is now: 5
Dropped a record, the count is now: 4
Dropped a record, the count is now: 3
Dropped a record, the count is now: 2
Dropped a record, the count is now: 1
Dropped a record, the count is now: 0
Dropped a record, the count is now: -1
counter record: 0x7f909f7fc010
The final record was dropped
Is this actually safe?
The unsafe guide says:
Raw pointers have much fewer guarantees than other pointer types offered by the Rust language and libraries. For example, they
... - are considered sendable (if their contents is considered sendable), so the compiler offers no assistance with ensuring their use is thread-safe; for example, one can concurrently access a
*mut int
from two threads without synchronization.
However...
Going the opposite direction, from
*const
to a reference&
, is not safe. A&T
is always valid, and so, at a minimum, the raw pointer*const T
has to be a valid to a valid instance of typeT
. Furthermore, the resulting pointer must satisfy the aliasing and mutability laws of references.
It looks like although the example above 'works', it's actually undefined behavior. In converting the *const i32
to a an &i32
to increment and decrement the reference count, the &i32
must satisfy the pointer aliasing rules; which it will not, as multiple Foo
s can be dropped at the same time (potentially, although not specifically in the example above).
How do you "correctly" implement this sort of behavior in a way that doesn't result in undefined behavior?