Using a method that is not part of a trait (Atomic store)

I have the problem that Atomic seems not to provide a trait interface for its methods.
I don't know hot to specify the type that matches all Atomic types.
for example here T should be Atomic:

pub struct Shared<T> where T: Send + Sync {
    value: Arc<T>,
}

impl<T: Send + Sync> Shared<T> {
    pub fn set_value(&self, t: T) {
        T::store(self.value,t, Ordering::Relaxed);
    }
}

With the std library alone you can't do that, but there are crates for it such as atomic and atomic-traits.

1 Like

thank you for the answer. atomic-traits looks good. but it doesn't implement the trait for AtomicF32 of the AtomicFloat crate. Is it possible to extend atomic-traits?

It looks like you can implement atomic_traits::Atomic for the AtomicF32 and AtomicF64, but I haven't tried it.


Oh, wait, that would violate the orphan rules. So the best thing would be to convince the owner of one of those crates to implement the trait, under a feature flag. Or you could try to contribute that change to one of the crates.

1 Like

…or at that point, just create your own trait.

what is the rational of the orphan rules?

The point of the coherence rules is to prevent multiple implementations of the same trait for the same type (which would lead to obvious ambiguity).

One workaround for the orphan rules is to create newtypes (wrapper structs) for AtomicF32 and AtomicF64 that implement the Atomic trait. The wrappers can also implement Deref to make them easier to use. Here's a quick partial example.

use std::sync::atomic::Ordering;
use atomic_traits::Atomic;

struct AtomicF32(atomic_float::AtomicF32);

impl Deref for AtomicF32 {
    type Target = atomic_float::AtomicF32;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl Atomic for AtomicF32 {
    type Type = f32;

    fn new(v: Self::Type) -> Self {
        AtomicF32(atomic_float::AtomicF32::new(v))
    }

    fn swap(
        &self,
        val: Self::Type,
        order: std::sync::atomic::Ordering,
    ) -> Self::Type {
        self.0.swap(val, order)
    }

    // TODO: implement the rest of the interface methods...
}

// Now you can use AtomicF32 as a generic

fn f<T, A: Atomic<Type = T>>(a: &A, v: T) -> T {
    a.swap(v, Ordering::Relaxed)
}

let a = AtomicF32::new(1.23);
let v = f(&a, 2.34);
assert_eq!(v, 1.23);

Edit: rename Atomic32 -> AtomicF32

The rational of the orphan rules in particular is to balance what both upstream and downstream can implement, to minimize the cases of new implementations being a major breaking change (by causing multiple implementations of the same trait for the same type).

For example, if you could write the suggested implementation, then I could write the implementation. If both of our crates ended up as dependencies in some third project, there were be overlapping implementations. One of the goals is to make this "incompatible crates due to coherence" situation impossible.

There's a ton of reading on the topic/history here if you're so inclined.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.