The goal of the following rather lengthy chunk of sample code is to define a type ScopedCell<T>
that you use as a static
variable, you set
from a reference to a local variable near the top of your call chain, and you can then get
from deep within the call chain without having to pass the reference all the way down. Yes, this is not entirely a safe thing to do. Please leave that aside for now, it's not relevant to the problem.
The problem is that I need two concrete types to be T: Mutex<LineWriter<File>>>
and str
. And I tried to be clever about this, because it occurred to me that when T is Sized
, ScopedCell<T>
could be implemented using AtomicPtr
instead of another Mutex
. But, as you all probably know better than me already, it ain't easy to vary the type of a field of a generic struct based on a generic parameter.
This is as far as I got:
`ScopedCell` implementation and sample uses (long)
use std::marker::PhantomData;
use std::sync::Mutex;
use std::sync::atomic::{AtomicPtr, Ordering};
use std::mem::transmute;
use std::ptr;
use std::fs::File;
use std::io::LineWriter;
static PROGNAME: ScopedCell<str> = ScopedCell::new();
pub fn set_progname(progname: &str) -> ScopedCellGuard<str> {
PROGNAME.set(progname)
}
pub unsafe fn get_progname() -> Option<&'static str> {
PROGNAME.get()
}
static WRITER: ScopedCell<Mutex<LineWriter<File>>> = ScopedCell::new();
pub fn set_writer(progname: &Mutex<LineWriter<File>>) -> ScopedCellGuard<Mutex<LineWriter<File>>> {
WRITER.set(progname)
}
pub unsafe fn writer() -> Option<&'static Mutex<LineWriter<File>>> {
WRITER.get()
}
/// Helper trait for ScopedCell which enables it to hold both
/// sized and (some) unsized types. Sized types can use AtomicPtr,
/// unsized types require a mutex.
trait ScopedCellTypes {
type Holder;
type Cell;
}
impl<T> ScopedCellTypes for T where T: Sized {
type Holder = AtomicPtr<T>;
type Cell = ScopedCell<T>;
}
impl ScopedCellTypes for str {
type Holder = Mutex<Option<&'static str>>;
type Cell = ScopedCell<str>;
}
/// static THING: ScopedCell<T> holds a nullable reference to T which
/// is Some within a particular dynamic scope.
pub(crate) struct ScopedCell<T: ScopedCellTypes + ?Sized> {
holder: <T as ScopedCellTypes>::Holder,
}
impl<T: Sized> ScopedCell<T> {
/// Create a new empty ScopedCell.
pub const fn new() -> Self {
Self { holder: AtomicPtr::new(ptr::null_mut()) }
}
/// Get the current value of the ScopedCell.
/// The actual lifetime of this value is not available, so caller
/// is responsible for ensuring that it does not hold onto the
/// reference longer than it actually lives.
unsafe fn get(&self) -> Option<&'static T> {
// SAFETY: holder is either null or was initialized from a valid
// reference by `set`.
unsafe { self.holder.load(Ordering::Acquire).cast_const().as_ref() }
}
/// Store `val` in self. Returns a guard object which will clear
/// self again when it is dropped, with lifetime bounded by the
/// lifetime of `val`, ensuring that `get` will never return a
/// dangling reference.
fn set<'a, 'b: 'a>(&'b self, val: &'a T) -> ScopedCellGuard<'a, T> {
// SAFETY: `val` is a valid reference and the guard is
// responsible for erasing our copy at the right time.
// The cast_mut here is paired with a cast_const in get();
// a mutable reference is never exposed.
self.holder.store((val as *const T).cast_mut(), Ordering::Release);
ScopedCellGuard::<T>::new(self)
}
/// Erase the value stored in self, if any. Normally not necessary
/// to call explicitly.
fn clear(&self) {
self.holder.store(ptr::null_mut(), Ordering::Release);
}
}
impl ScopedCell<str> {
pub const fn new() -> Self {
Self { holder: Mutex::new(None) }
}
unsafe fn get(&self) -> Option<&'static str> {
*self.holder.lock().unwrap()
}
fn set<'a, 'b: 'a>(&'b self, val: &'a str) -> ScopedCellGuard<'a, str> {
*self.holder.lock().unwrap() = Some(unsafe { transmute(val) });
ScopedCellGuard::<str>::new(self)
}
fn clear(&self) {
*self.holder.lock().unwrap() = None;
}
}
pub(crate) struct ScopedCellGuard<'a, T: ScopedCellTypes + ?Sized> {
cell: &'a <T as ScopedCellTypes>::Cell,
lifetime: PhantomData<&'a T>,
}
impl<'a, T: ScopedCellTypes + ?Sized> ScopedCellGuard<'a, T> {
fn new(cell: &'a <T as ScopedCellTypes>::Cell) -> Self {
Self { cell, lifetime: PhantomData }
}
}
impl<'a, T: ScopedCellTypes + ?Sized> Drop for ScopedCellGuard<'a, T> {
fn drop(&mut self) {
self.cell.clear()
}
}
And here's the errors I'm getting:
error[E0034]: multiple applicable items in scope
--> src/lib.rs:10:48
|
10 | static PROGNAME: ScopedCell<str> = ScopedCell::new();
| ^^^ multiple `new` found
|
note: candidate #1 is defined in an impl for the type `ScopedCell<T>`
--> src/lib.rs:57:5
|
57 | pub const fn new() -> Self {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
note: candidate #2 is defined in an impl for the type `ScopedCell<str>`
--> src/lib.rs:92:5
|
92 | pub const fn new() -> Self {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
error[E0034]: multiple applicable items in scope
--> src/lib.rs:20:59
|
20 | static WRITER: ScopedCell<LineWriter<File>> = ScopedCell::new();
| ^^^ multiple `new` found
|
note: candidate #1 is defined in an impl for the type `ScopedCell<T>`
--> src/lib.rs:57:5
|
57 | pub const fn new() -> Self {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
note: candidate #2 is defined in an impl for the type `ScopedCell<str>`
--> src/lib.rs:92:5
|
92 | pub const fn new() -> Self {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
error[E0599]: no method named `clear` found for reference `&'a <T as ScopedCellTypes>::Cell` in the current scope
--> src/lib.rs:121:19
|
121 | self.cell.clear()
| ^^^^^ method not found in `&<T as ScopedCellTypes>::Cell`
The E0034 errors are annoying -- I spelled out the exact type I wanted on the left side of the assignment, that should be enough information to pick the correct impl -- but easily surmountable (by using the turbofish on the right side). The E0599 error, on the other hand, has stopped me completely. No matter what T
is, as long as it satisfies the ScopedCellTypes + ?Sized
bound, the concrete type <T as ScopedCellTypes>::Cell
is one or the other variant of ScopedCell<T>
and they both have a clear
method, so there should be no problem. I tried moving get
, set
and clear
to a trait but that helped not at all (it actually made the errors quite a bit messier).
(I don't even really understand why the indirection through ScopedCellTypes::<T>::Cell
is necessary in the first place. I originally had cell: &ScopedCell<T>
but then the compiler coughed up a mess of errors about T
not necessarily being Sized
, which is true but does not appear to be relevant given that both ScopedCell
and ScopedCellGuard
are impl'd very specifically for the same ScopedCellTypes + ?Sized
bound...)
Any help would be most appreciated.