Reputation: 6382
I am learning unsafe Rust and trying to create an atomic backed by a pointer to some unsafe memory (e.g. a buffer from C or memory mapped file).
I tried this:
use std::sync::atomic::{AtomicI64, Ordering};
fn main() -> () {
let mut v = vec![1i64, 2i64];
let ptr = &mut v[0] as *mut i64;
unsafe {
let a = std::mem::transmute::<*mut i64, AtomicI64>(ptr);
println!("{}", a.load(Ordering::Relaxed));
}
}
But it prints the address of the pointer (e.g. 2119547391296
) instead of 1
.
What is the correct way to create an atomic located in some external buffer?
I want the same functionality such as e.g. C# Interlocked.CompareExchange(ref *(long*)ptr, ...)
, so maybe there are other ways to get lock-free sync primitives in Rust?
Update:
It looks like I need std::intrinsics::{*}
, but they are not available in stable Rust.
Update 2:
This compiles and prints 1 2 2
(i.e. v[0]
is updated as expected via AtomicI64
created via pointer cast and then dereferencing AtomicI64
via & *ptr
). But is this correct?
use std::sync::atomic::{AtomicI64, Ordering};
fn main() -> () {
let v = vec![1i64, 2i64];
let ptr = &v[0] as *const i64 as *const AtomicI64;
unsafe {
let a = & *ptr;
println!("{}", a.load(Ordering::SeqCst));
a.fetch_add(1i64, Ordering::SeqCst);
println!("{}", a.load(Ordering::SeqCst));
println!("{}", v[0]);
}
}
Upvotes: 1
Views: 1641
Reputation: 40904
The documentation for AtomicI64
says this:
This type has the same in-memory representation as the underlying integer type, i64.
However, you're trying to transmute a pointer to an i64
to an AtomicI64
:
unsafe {
let a = std::mem::transmute::<*mut i64, AtomicI64>(ptr);
// is a pointer ^^^^^^^^
// ^^^^^^^^^ is not a pointer
}
Instead, you'd need to transmute *mut i64
into a pointer or reference to AtomicI64
.
This can be implemented like this (safe and unsafe variants):
// if we have a mut reference, it must have unqiue ownership over the
// referenced data, so we can safely cast that into an immutable reference
// to AtomicI64
fn make_atomic_i64<'a>(src: &'a mut i64) -> &'a AtomicI64 {
unsafe {
&*(src as *mut i64 as *const AtomicI64)
}
}
// if we have a mut pointer, we have no guarantee of ownership or lifetime, and
// therefore it's unsafe to cast into an immutable reference to AtomicI64
unsafe fn make_ptr_atomic_i64<'a>(src: *mut i64) -> &'a AtomicI64 {
&*(src as *const AtomicI64)
}
use std::sync::atomic::{AtomicI64, Ordering};
fn main() -> () {
// declare underlying buffer
let mut v = vec![1i64, 2i64];
{
// get atomic safely
let atomic = make_atomic_i64(&mut v[0]);
// try to access atomic
println!("{}", atomic.swap(10, Ordering::Relaxed)); // = 1
}
unsafe {
// get atomic unsafely
let atomic = make_ptr_atomic_i64(&mut v[0] as *mut i64);
// try to access atomic
println!("{}", atomic.swap(100, Ordering::Relaxed)); // = 10
}
// print final state of variable
println!("{}", v[0]); // = 100
}
Upvotes: 6
Reputation: 42849
AtomicPtr
will do the job. You can only construct it from a mutable pointer because it must own the data pointed to.
If you have a shared/sharable raw pointer (a const one), it cannot be atomic by design. If you want to share a pointer, it must be an AtomicPtr
behind an Arc
.
Just as a reminder, mutable reference/pointer equals unique, and const reference/pointer equals shared. If you break this rule, your program is undefined behavior.
Upvotes: 0