Note that for this case, using an Option
(or another mem::replace
-like take pattern) can replace the unsafe
-ty:
Option
struct Worker {
/// Wrapped in an `Option` to be able to drop it earlier (on `Drop`)
sender: Option< Sender<Item> >,
thread: jthread::Handle,
}
impl Worker {
fn sender (self: &'_ mut Self) -> &'_ mut Sender<Item>
{
self.sender.as_mut().expect("Already dropped `sender`??")
}
}
impl Drop for Worker {
fn drop (self: &'_ mut Self)
{
// drop the `sender` before the rest / the `thread` handle.
drop(self.sender.take());
}
}
▼ mem::replace
struct Worker {
sender: Sender<Item>,
// ...
thread: jthread::Handle,
}
impl Drop for Worker {
fn drop (self: &'_ mut Self)
{
// drop the `sender` before the rest / the `thread` handle.
drop(::core::mem::replace(&mut self.sender, channel::new(...).0));
}
}
That is, I feel like the moment this is perceived as a "drop earlier" kind of issue, rather than a drop later, the take pattern becomes more intuitive / idiomatic.
Regarding the OP itself, I would say that a comment on either sender
or thread
reminding people to pay unusual attention to declaration order in that it suddenly is semantically meaningful is a quite decent solution
-
in that regard, a procedural macro to enforce field orders would solve this issue and be quite trivial to implement:
#[derive(FieldOrderSensitive)] // Hints at something non-trivial going on here struct Worker { sender: Sender<Item>, #[assert_declared_after(.sender, reason = "needs to be dropped after it")] thread: jthread::Handle, }