Is there a way to specify only dynamically sized for type parameter?

I am working on a container arc::Unsafe<T: >. It's like Arc but can be used without mutex to boost the program. I want it to support both dyn and Sized. However, the pointer in it is ?Sized. I know that I can use Box<> to wrap it but it allocates unnessarry heap memory.
I can use T: ?Sized to suggest the size of T is uncertain. However, I cannot add another specification for T: Sized. How should I treat T: !Sized and T:Sized separately?

A minimal version would be helpful...

Would it make sense to use a &dyn arc::Unsafe<T>?

It's not useful since arc itself is a smart pointer.
See the last test.

use std::cell::UnsafeCell;
use std::marker::PhantomData;
use std::ops::{Deref, DerefMut};
use std::ptr::NonNull;
use std::sync::atomic::{AtomicUsize, Ordering};

// FIXME it crashes sometimes especially when ending the program
// TODO support dyn types without Box
struct ArcInner<T: ?Sized> {
    reference: AtomicUsize,
    phantom:   PhantomData<T>,
    val:       UnsafeCell<T>,
}

impl<T> ArcInner<T> {
    fn new(val: T) -> Self {
        Self {
            val:       UnsafeCell::new(val),
            reference: AtomicUsize::new(1),
            phantom:   PhantomData::default(),
        }
    }
}

pub struct Unsafe<T> {
    inner: Option<NonNull<ArcInner<T>>>,
}

impl<T> Unsafe<T> {
    pub fn new(val: T) -> Self {
        Self {
            inner: NonNull::new(Box::into_raw(Box::new(ArcInner::new(val)))),
        }
    }

    pub fn empty() -> Self { Self { inner: None } }

    pub fn is_null(&self) -> bool { self.inner.is_none() }
}

impl<T> Drop for Unsafe<T> {
    fn drop(&mut self) {
        unsafe {
            loop {
                if self.is_null() {
                    return;
                }
                let r = self.inner.unwrap().as_mut().reference.load(Ordering::Acquire);
                if r > 0 {
                    if self.inner.unwrap().as_mut().reference.compare_and_swap(r, r - 1, Ordering::AcqRel) == r {
                        if r == 1 {
                            drop(Box::from_raw(self.inner.unwrap().as_ptr()))
                        }
                        return;
                    }
                }
            }
        }
    }
}

impl<T> Clone for Unsafe<T> {
    fn clone(&self) -> Self {
        unsafe {
            loop {
                if self.is_null() {
                    return Self::empty();
                }
                let r = self.inner.unwrap().as_mut().reference.load(Ordering::Acquire);
                if self.inner.unwrap().as_mut().reference.compare_and_swap(r, r + 1, Ordering::AcqRel) == r {
                    return Self { inner: self.inner };
                }
            }
        }
    }
}

impl<T> Deref for Unsafe<T> {
    type Target = T;

    fn deref(&self) -> &Self::Target { unsafe { &*self.inner.unwrap().as_ref().val.get() } }
}

impl<T> DerefMut for Unsafe<T> {
    fn deref_mut(&mut self) -> &mut Self::Target { unsafe { &mut *self.inner.unwrap().as_ref().val.get() } }
}

unsafe impl<T> Sync for Unsafe<T> {}

unsafe impl<T> Send for Unsafe<T> {}

impl<T> Default for Unsafe<T> {
    fn default() -> Self { Self::empty() }
}

#[cfg(test)]
mod tests {
    use crate::arc;

    #[test]
    #[allow(unused)]
    fn arc_unsafe() {
        struct A;
        impl A {
            fn test(&self) {}
        }
        let a = arc::Unsafe::new(A);
        let x;
        {
            let b = arc::Unsafe::clone(&a);
            x = &*b;
        }
        // x.test(); should not compile
        let mut c = arc::Unsafe::clone(&a);
        std::thread::spawn(move || {
            *c = A;
        });
        let d = arc::Unsafe::from(a);
    }

    #[test]
    fn arc_trait() {
        trait A {}

        struct B;
        struct C;

        impl A for B {}
        impl A for C {}

        // FIXME
        // let mut p: arc::Unsafe<dyn A> = arc::Unsafe::new(B);
        // p = arc::Unsafe::new(C);
        let mut p: arc::Unsafe<Box<dyn A>> = arc::Unsafe::new(Box::new(B));
    }
}

Btw, auto conversion like this would also solve the problem. Though I am not sure how to implement it

trait Foo {}
struct Bar;
impl Foo for Bar {}
let b: Box<dyn Foo> = Box::new(Bar);

It looks like you'll have to use a Box. If you create an Unsafe<T> on a thread, T will be stored on the stack. Once the function exists, Ts memory will no longer be valid without Box (stack will be used by another function). If you know that the thread will always exist, you can just use regular references...

If T is unsized it must be accessed by-reference, and AFAIK it can't currently be on the stack (no alloca in rust at the moment). You could try solving this by creating an enum like Cow, which could be either an Owned instance (Box<T>) or reference (&T). Cow itself has other restriction for cloning (to implement to_owned, which you won't need), so it might not be useful in this case.

enum MyCow<'a, T: ?Sized> {
    Owned(Box<T>),
    Ref(&'a T),
}

Note that your use of atomics my introduce a race condition. You should probably use fetch_add/fetch_sub instead.

Do you notice that I have used Box::into_raw(Box::new(ArcInner::new(val))) to allocate on the heap?

This requires the unstable CoerceUnsized trait.

1 Like

I didn't notice, sorry about that

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.