V.B.
V.B.

Reputation: 6382

How to create atomic from unsafe memory in Rust

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

Answers (2)

Freyja
Freyja

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)
}

Example:

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

Boiethios
Boiethios

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

Related Questions