Skip to content

Simple API #2

@daniel-pfeiffer

Description

@daniel-pfeiffer

Edit: Put this on hold, as Netstack3 does indeed have a tree, obfuscated as a long linear list. This needs to be made more powerful!

Since using this crate is very boilerplatey (and typo-prone as per issue #1) I’ve wrapped all that – inspired by just your holder struct. I see no tree, so I opted to name this “ladder” (could be staircase, elevator…) All it takes is

lock_ladder! {
    pub(crate) Locks {
        LockA: Mutex<u8>,
        LockB: Mutex<u32>,
        LockC: RwLock<MyStruct> = MyStruct(true),
    }
}

// Accessing locked state looks like this:
let state = Locks::new();

// Create a new lock session with the "root" lock level (empty tuple).
let mut locked_unlocked = state.locked();

// Access locked state.
let (a, mut locked_a) = locked_unlocked.lock_and::<LockA>();
let (b, mut locked_b) = locked_a.lock_and::<LockB>();
{
    let cw = locked_b.write_lock::<LockC>();
}
// This would not compile with locked_a, but, after dropping cw, locked_b is again the top
let cr = locked_b.read_lock::<LockC>();

or

lock_ladder! {
    const Locks {
        LockA: Mutex<u8> = 0,
        LockB: Mutex<u32> = 0,
        LockC: RwLock<MyStruct> = MyStruct(true),
    }
}
static STATE: Locks = Locks::new();
let mut locked_unlocked = STATE.locked();

For now it’s a PoC. It needs some tweaking to make it exportable from your crate and maybe split it into 2 macros. For a PR to add it, I’d need to know what file-name you’d like to have and what changes you’d like to see.

/**
Wrap many locks in a typestate ladder, so they can only be obtained in that order.
Syntax is an optional visibility & `const` ans a name and a block. The block has any number of comma-separated
lock-specs.  Each lock-spec is a handle-type-name ":" lock-pseudotype, and optionally: "=" initial value.  If an initial
value, which is only for the inner Data type, is not given, the inner Data type must implement `Default`. That currently
prevents it from being `const`.
```
lock_ladder! {
    pub(crate) Locks {
        LockA: Mutex<u8>,
        LockB: Mutex<u32>,
        LockC: RwLock<MyStruct> = MyStruct(true),
    }
}
let state = Locks::new();
let mut locked_unlocked = state.locked();
```
or
```
lock_ladder! {
    const Locks {
        LockA: Mutex<u8> = 0,
        LockB: Mutex<u32> = 0,
        LockC: RwLock<MyStruct> = MyStruct(true),
    }
}
static STATE: Locks = Locks::new();
let mut locked_unlocked = STATE.locked();
```
`Mutex` and `RwLock` are macro-names here. They instruct this macro about those types.
For other locks you need to provide similar macros, like `TokioMutex` or `SnapshotLock`.
*/
macro_rules! lock_ladder {
    ($vis:vis const $holder:ident $spec:tt) => {
        lock_ladder!(@ $vis const, $holder $spec);
    };
    ($vis:vis $holder:ident $spec:tt) => {
        lock_ladder!(@ $vis, $holder $spec);
    };
    (@ $vis:vis $($const:ident)?, $holder:ident { $($handle:ident: $lock:ident<$ty:ty> $(= $default:expr)?,)+ $(,)? }) => {
        $($vis enum $handle {})+

        lock_ladder!(< $($handle)+);

        // reuse $handle+ as field names, but they don’t follow the convention
        #[allow(non_snake_case)]
        $vis struct $holder {
            $($handle: $lock!($ty)),+
        }

        impl $holder {
            $vis $($const)? fn new() -> Self {
                Self {
                    // Are there any such types without a 1-param new()?
                    // Also use Default::default() for Data only, as some crates don’t impl Default.
                    $($handle: <$lock!($ty)>::new(lock_ladder!(= $($default)?))),+
                }
            }

            $vis fn locked(&self) -> lock_tree::Locked<&Self, lock_tree::Unlocked> {
                lock_tree::Locked::new(self)
            }
        }

        $($lock!($holder, $handle, $ty);)+
    };
    (= $default:expr) => { $default };
    (=) => { Default::default() };

    (<< $handle1:ident $handle2:ident $($handle:ident)*) => {
        lock_tree::impl_lock_after!($handle1 => $handle2);
        lock_ladder!(<< $handle2 $($handle)*);
    };
    (<< $handle:ident) => {};
    (< $handle1:ident $($handle:ident)*) => {
        impl lock_tree::relation::LockAfter<lock_tree::Unlocked> for $handle1 {}
        lock_ladder!(<< $handle1 $($handle)*);
    };
}

// Have one macro like this for each similar type: TokioMutex, OrderedMutex…
macro_rules! Mutex {
    ($ty:ty) => {
        std::sync::Mutex<$ty>
    };
    ($holder:ident, $handle:ident, $ty:ty) => {
        impl lock_tree::lock::LockFor<$handle> for $holder {
            type Data = $ty;

            type Guard<'l>
                = std::sync::MutexGuard<'l, $ty>
            where
                Self: 'l;

            fn lock(&self) -> Self::Guard<'_> {
                self.$handle.lock().unwrap()
            }
        }
    };
}

// Have one macro like this for each similar type: TokioRwLock, SnapshotLock…
macro_rules! RwLock {
    ($ty:ty) => {
        std::sync::RwLock<$ty>
    };
    ($holder:ident, $handle:ident, $ty:ty) => {
        impl lock_tree::lock::RwLockFor<$handle> for $holder {
            type Data = $ty;

            type ReadGuard<'l>
                = std::sync::RwLockReadGuard<'l, $ty>
            where
                Self: 'l;

            type WriteGuard<'l>
                = std::sync::RwLockWriteGuard<'l, $ty>
            where
                Self: 'l;

            fn read_lock(&self) -> Self::ReadGuard<'_> {
                self.$handle.read().unwrap()
            }

            fn write_lock(&self) -> Self::WriteGuard<'_> {
                self.$handle.write().unwrap()
            }
        }
    };
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions