From 12b8022d1033433eb65f16dbdde82e714867cda9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Mon, 11 Oct 2021 23:48:11 +0200 Subject: [PATCH 1/9] Add ability to spawn new senders from the receiver. --- crossbeam-channel/src/channel.rs | 37 +++++++++++++++++++++++++++ crossbeam-channel/src/counter.rs | 14 ++++++++++ crossbeam-channel/src/flavors/list.rs | 11 ++++++++ crossbeam-channel/src/lib.rs | 4 +-- crossbeam-channel/tests/list.rs | 19 +++++++++++++- 5 files changed, 82 insertions(+), 3 deletions(-) diff --git a/crossbeam-channel/src/channel.rs b/crossbeam-channel/src/channel.rs index 8988235db..a5a7c546a 100644 --- a/crossbeam-channel/src/channel.rs +++ b/crossbeam-channel/src/channel.rs @@ -3,6 +3,7 @@ use std::fmt; use std::iter::FusedIterator; use std::mem; +use std::ops::Deref; use std::panic::{RefUnwindSafe, UnwindSafe}; use std::sync::Arc; use std::time::{Duration, Instant}; @@ -720,6 +721,7 @@ impl UnwindSafe for Receiver {} impl RefUnwindSafe for Receiver {} impl Receiver { + /// Attempts to receive a message from the channel without blocking. /// /// This method will either receive a message from the channel immediately or return an error @@ -1506,3 +1508,38 @@ pub(crate) unsafe fn read(r: &Receiver, token: &mut Token) -> Result chan.read(token), } } + + +/// An [unbounded] channel whose receiver is a [ReconnectableReceiver]. +pub fn unbounded_reconnectable() -> (Sender, ReconnectableReceiver) { + let (s, r) = unbounded(); + (s, ReconnectableReceiver(r)) +} + +/// A receiver that can survive periods of time where no [Sender]s +/// are live. New senders may be created from the receiver directly +/// ([Self::new_sender]). The channel is only deallocated when the +/// last receiver dies. If there are live senders at that point, they +/// start producing [SendError]s as usual. +#[derive(Debug)] +pub struct ReconnectableReceiver(Receiver); + +impl ReconnectableReceiver { + /// Returns a new sender for this receiver. + pub fn new_sender(&self) -> Sender { + match &self.0.flavor { + ReceiverFlavor::Array(chan) => Sender { flavor: SenderFlavor::Array(chan.new_sender(|_| todo!())) }, + ReceiverFlavor::List(chan) => Sender { flavor: SenderFlavor::List(chan.new_sender(|c| c.reconnect_senders())) }, + ReceiverFlavor::Zero(chan) => Sender { flavor: SenderFlavor::Zero(chan.new_sender(|_| todo!())) }, + _ => unreachable!("This type cannot be built with at/never/tick"), + } + } +} + +impl Deref for ReconnectableReceiver { + type Target = Receiver; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} diff --git a/crossbeam-channel/src/counter.rs b/crossbeam-channel/src/counter.rs index 2c27f7c6b..a5baf66bd 100644 --- a/crossbeam-channel/src/counter.rs +++ b/crossbeam-channel/src/counter.rs @@ -94,6 +94,7 @@ pub(crate) struct Receiver { } impl Receiver { + /// Returns the internal `Counter`. fn counter(&self) -> &Counter { unsafe { &*self.counter } @@ -127,6 +128,19 @@ impl Receiver { } } } + + + pub(crate) fn new_sender(&self, reconnect: impl FnOnce(&C) -> bool) -> Sender { + if 0 == self.counter().senders.fetch_add(1, Ordering::SeqCst) { + // we're the first sender to be created + reconnect(&self.counter().chan); + self.counter().destroy.store(false, Ordering::Relaxed); + } + + Sender { + counter: self.counter, + } + } } impl ops::Deref for Receiver { diff --git a/crossbeam-channel/src/flavors/list.rs b/crossbeam-channel/src/flavors/list.rs index 5056aa431..5d0852769 100644 --- a/crossbeam-channel/src/flavors/list.rs +++ b/crossbeam-channel/src/flavors/list.rs @@ -544,6 +544,17 @@ impl Channel { } } + /// Reconnects senders. There is no need to notify threads + /// as nobody is currently blocking, since we were in an + /// idle phase. + /// + /// Returns `true` if this call reconnected the channel. + pub(crate) fn reconnect_senders(&self) -> bool { + let tail = self.tail.index.fetch_and(!MARK_BIT, Ordering::SeqCst); + + tail & MARK_BIT == 0 + } + /// Disconnects receivers. /// /// Returns `true` if this call disconnected the channel. diff --git a/crossbeam-channel/src/lib.rs b/crossbeam-channel/src/lib.rs index cc1ef112f..5bb2c8bfd 100644 --- a/crossbeam-channel/src/lib.rs +++ b/crossbeam-channel/src/lib.rs @@ -358,9 +358,9 @@ cfg_if! { } pub use crate::channel::{after, at, never, tick}; - pub use crate::channel::{bounded, unbounded}; + pub use crate::channel::{bounded, unbounded, unbounded_reconnectable}; pub use crate::channel::{IntoIter, Iter, TryIter}; - pub use crate::channel::{Receiver, Sender}; + pub use crate::channel::{Receiver, Sender, ReconnectableReceiver}; pub use crate::select::{Select, SelectedOperation}; diff --git a/crossbeam-channel/tests/list.rs b/crossbeam-channel/tests/list.rs index 6673e4b24..45bd08bb2 100644 --- a/crossbeam-channel/tests/list.rs +++ b/crossbeam-channel/tests/list.rs @@ -6,7 +6,7 @@ use std::sync::atomic::Ordering; use std::thread; use std::time::Duration; -use crossbeam_channel::{select, unbounded, Receiver}; +use crossbeam_channel::{select, unbounded, unbounded_reconnectable, Receiver}; use crossbeam_channel::{RecvError, RecvTimeoutError, TryRecvError}; use crossbeam_channel::{SendError, SendTimeoutError, TrySendError}; use crossbeam_utils::thread::scope; @@ -200,6 +200,23 @@ fn recv_after_disconnect() { assert_eq!(r.recv(), Err(RecvError)); } +#[test] +fn zero_receiver_revival() { + let (s, r) = unbounded_reconnectable(); + + s.send(1).unwrap(); + + drop(s); + + assert_eq!(r.recv(), Ok(1)); + assert_eq!(r.recv(), Err(RecvError)); + + let s = r.new_sender(); + + s.send(1).unwrap(); + assert_eq!(r.recv(), Ok(1)); +} + #[test] fn len() { let (s, r) = unbounded(); From 9eed66904f969156dedad4eef61ce91d23b9cccb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Sun, 17 Oct 2021 22:45:04 +0200 Subject: [PATCH 2/9] Cleanup API --- crossbeam-channel/src/channel.rs | 73 ++++++++++++++++++++------------ crossbeam-channel/src/lib.rs | 5 ++- crossbeam-channel/tests/list.rs | 4 +- 3 files changed, 52 insertions(+), 30 deletions(-) diff --git a/crossbeam-channel/src/channel.rs b/crossbeam-channel/src/channel.rs index a5a7c546a..61951bf57 100644 --- a/crossbeam-channel/src/channel.rs +++ b/crossbeam-channel/src/channel.rs @@ -1509,37 +1509,58 @@ pub(crate) unsafe fn read(r: &Receiver, token: &mut Token) -> Result() -> (Sender, ReconnectableReceiver) { - let (s, r) = unbounded(); - (s, ReconnectableReceiver(r)) -} + // The intended usage of this module is just to replace any `use crossbeam_channel::...` + // with `use crossbeam_channel::reconnectable::...` and have everything work out of the box. + + pub use crate::*; + use super::*; + + use super::Receiver as NormalReceiver; + + /// An [unbounded](super::unbounded) channel whose receiver + /// also is reconnectable. + pub fn unbounded() -> (Sender, Receiver) { + let (s, r) = super::unbounded(); + (s, Receiver(r)) + } -/// A receiver that can survive periods of time where no [Sender]s -/// are live. New senders may be created from the receiver directly -/// ([Self::new_sender]). The channel is only deallocated when the -/// last receiver dies. If there are live senders at that point, they -/// start producing [SendError]s as usual. -#[derive(Debug)] -pub struct ReconnectableReceiver(Receiver); - -impl ReconnectableReceiver { - /// Returns a new sender for this receiver. - pub fn new_sender(&self) -> Sender { - match &self.0.flavor { - ReceiverFlavor::Array(chan) => Sender { flavor: SenderFlavor::Array(chan.new_sender(|_| todo!())) }, - ReceiverFlavor::List(chan) => Sender { flavor: SenderFlavor::List(chan.new_sender(|c| c.reconnect_senders())) }, - ReceiverFlavor::Zero(chan) => Sender { flavor: SenderFlavor::Zero(chan.new_sender(|_| todo!())) }, - _ => unreachable!("This type cannot be built with at/never/tick"), + /// A [receiver](super::Receiver) that can survive periods + /// of time where no [Sender]s are alive. New senders may + /// be created from the receiver directly ([Self::new_sender]). + /// The channel is only deallocated when the last receiver dies. + /// If there are live senders at that point, they start producing + /// [SendError]s as usual. + #[derive(Debug)] + pub struct Receiver(NormalReceiver); + + impl Receiver { + + /// Returns a new sender that communicates with this + /// receiver. + pub fn new_sender(&self) -> Sender { + match &self.0.flavor { + ReceiverFlavor::Array(chan) => Sender { flavor: SenderFlavor::Array(chan.new_sender(|_| todo!())) }, + ReceiverFlavor::List(chan) => Sender { flavor: SenderFlavor::List(chan.new_sender(|c| c.reconnect_senders())) }, + ReceiverFlavor::Zero(chan) => Sender { flavor: SenderFlavor::Zero(chan.new_sender(|_| todo!())) }, + _ => unreachable!("This type cannot be built with at/never/tick"), + } } } -} -impl Deref for ReconnectableReceiver { - type Target = Receiver; + impl Deref for self::Receiver { + type Target = NormalReceiver; - fn deref(&self) -> &Self::Target { - &self.0 + fn deref(&self) -> &Self::Target { + &self.0 + } } } + diff --git a/crossbeam-channel/src/lib.rs b/crossbeam-channel/src/lib.rs index 5bb2c8bfd..05afa560b 100644 --- a/crossbeam-channel/src/lib.rs +++ b/crossbeam-channel/src/lib.rs @@ -358,9 +358,10 @@ cfg_if! { } pub use crate::channel::{after, at, never, tick}; - pub use crate::channel::{bounded, unbounded, unbounded_reconnectable}; + pub use crate::channel::{bounded, unbounded}; pub use crate::channel::{IntoIter, Iter, TryIter}; - pub use crate::channel::{Receiver, Sender, ReconnectableReceiver}; + pub use crate::channel::{Receiver, Sender}; + pub use crate::channel::reconnectable; pub use crate::select::{Select, SelectedOperation}; diff --git a/crossbeam-channel/tests/list.rs b/crossbeam-channel/tests/list.rs index 45bd08bb2..c3d988c71 100644 --- a/crossbeam-channel/tests/list.rs +++ b/crossbeam-channel/tests/list.rs @@ -6,7 +6,7 @@ use std::sync::atomic::Ordering; use std::thread; use std::time::Duration; -use crossbeam_channel::{select, unbounded, unbounded_reconnectable, Receiver}; +use crossbeam_channel::{select, unbounded, reconnectable, Receiver}; use crossbeam_channel::{RecvError, RecvTimeoutError, TryRecvError}; use crossbeam_channel::{SendError, SendTimeoutError, TrySendError}; use crossbeam_utils::thread::scope; @@ -202,7 +202,7 @@ fn recv_after_disconnect() { #[test] fn zero_receiver_revival() { - let (s, r) = unbounded_reconnectable(); + let (s, r) = reconnectable::unbounded(); s.send(1).unwrap(); From b9e31193e76195f168f2f4ed7022d5cdefd38634 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Fri, 29 Oct 2021 15:45:28 +0200 Subject: [PATCH 3/9] Fix unstability warnings todo!() wasn't stable back in 1.36.0. I feel like unreachable!() is more appropriate anyway, as these code branches are unreachable until we implement the corresponding public function `bounded`. --- crossbeam-channel/src/channel.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crossbeam-channel/src/channel.rs b/crossbeam-channel/src/channel.rs index 61951bf57..4d920c0f6 100644 --- a/crossbeam-channel/src/channel.rs +++ b/crossbeam-channel/src/channel.rs @@ -1547,9 +1547,9 @@ pub mod reconnectable { /// receiver. pub fn new_sender(&self) -> Sender { match &self.0.flavor { - ReceiverFlavor::Array(chan) => Sender { flavor: SenderFlavor::Array(chan.new_sender(|_| todo!())) }, + ReceiverFlavor::Array(chan) => Sender { flavor: SenderFlavor::Array(chan.new_sender(|_| unreachable!())) }, ReceiverFlavor::List(chan) => Sender { flavor: SenderFlavor::List(chan.new_sender(|c| c.reconnect_senders())) }, - ReceiverFlavor::Zero(chan) => Sender { flavor: SenderFlavor::Zero(chan.new_sender(|_| todo!())) }, + ReceiverFlavor::Zero(chan) => Sender { flavor: SenderFlavor::Zero(chan.new_sender(|_| unreachable!())) }, _ => unreachable!("This type cannot be built with at/never/tick"), } } From ecbf92943828e6d3c73884443ba009454d3968c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Fri, 29 Oct 2021 15:47:27 +0200 Subject: [PATCH 4/9] cargo fmt --- crossbeam-channel/src/channel.rs | 17 ++++++++++------- crossbeam-channel/src/counter.rs | 2 -- crossbeam-channel/tests/list.rs | 2 +- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/crossbeam-channel/src/channel.rs b/crossbeam-channel/src/channel.rs index 4d920c0f6..5d80fb13f 100644 --- a/crossbeam-channel/src/channel.rs +++ b/crossbeam-channel/src/channel.rs @@ -721,7 +721,6 @@ impl UnwindSafe for Receiver {} impl RefUnwindSafe for Receiver {} impl Receiver { - /// Attempts to receive a message from the channel without blocking. /// /// This method will either receive a message from the channel immediately or return an error @@ -1520,8 +1519,8 @@ pub mod reconnectable { // The intended usage of this module is just to replace any `use crossbeam_channel::...` // with `use crossbeam_channel::reconnectable::...` and have everything work out of the box. - pub use crate::*; use super::*; + pub use crate::*; use super::Receiver as NormalReceiver; @@ -1542,14 +1541,19 @@ pub mod reconnectable { pub struct Receiver(NormalReceiver); impl Receiver { - /// Returns a new sender that communicates with this /// receiver. pub fn new_sender(&self) -> Sender { match &self.0.flavor { - ReceiverFlavor::Array(chan) => Sender { flavor: SenderFlavor::Array(chan.new_sender(|_| unreachable!())) }, - ReceiverFlavor::List(chan) => Sender { flavor: SenderFlavor::List(chan.new_sender(|c| c.reconnect_senders())) }, - ReceiverFlavor::Zero(chan) => Sender { flavor: SenderFlavor::Zero(chan.new_sender(|_| unreachable!())) }, + ReceiverFlavor::Array(chan) => Sender { + flavor: SenderFlavor::Array(chan.new_sender(|_| unreachable!())), + }, + ReceiverFlavor::List(chan) => Sender { + flavor: SenderFlavor::List(chan.new_sender(|c| c.reconnect_senders())), + }, + ReceiverFlavor::Zero(chan) => Sender { + flavor: SenderFlavor::Zero(chan.new_sender(|_| unreachable!())), + }, _ => unreachable!("This type cannot be built with at/never/tick"), } } @@ -1563,4 +1567,3 @@ pub mod reconnectable { } } } - diff --git a/crossbeam-channel/src/counter.rs b/crossbeam-channel/src/counter.rs index a5baf66bd..d641c869d 100644 --- a/crossbeam-channel/src/counter.rs +++ b/crossbeam-channel/src/counter.rs @@ -94,7 +94,6 @@ pub(crate) struct Receiver { } impl Receiver { - /// Returns the internal `Counter`. fn counter(&self) -> &Counter { unsafe { &*self.counter } @@ -129,7 +128,6 @@ impl Receiver { } } - pub(crate) fn new_sender(&self, reconnect: impl FnOnce(&C) -> bool) -> Sender { if 0 == self.counter().senders.fetch_add(1, Ordering::SeqCst) { // we're the first sender to be created diff --git a/crossbeam-channel/tests/list.rs b/crossbeam-channel/tests/list.rs index c3d988c71..38f3b6a4e 100644 --- a/crossbeam-channel/tests/list.rs +++ b/crossbeam-channel/tests/list.rs @@ -6,7 +6,7 @@ use std::sync::atomic::Ordering; use std::thread; use std::time::Duration; -use crossbeam_channel::{select, unbounded, reconnectable, Receiver}; +use crossbeam_channel::{reconnectable, select, unbounded, Receiver}; use crossbeam_channel::{RecvError, RecvTimeoutError, TryRecvError}; use crossbeam_channel::{SendError, SendTimeoutError, TrySendError}; use crossbeam_utils::thread::scope; From 058cd16dc7ea5d5d7ba37c939d78432c33c5e02b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Sun, 23 Jan 2022 18:47:59 +0100 Subject: [PATCH 5/9] Change unreachable to todo --- crossbeam-channel/src/channel.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crossbeam-channel/src/channel.rs b/crossbeam-channel/src/channel.rs index 5d80fb13f..8ae16eb93 100644 --- a/crossbeam-channel/src/channel.rs +++ b/crossbeam-channel/src/channel.rs @@ -1546,13 +1546,13 @@ pub mod reconnectable { pub fn new_sender(&self) -> Sender { match &self.0.flavor { ReceiverFlavor::Array(chan) => Sender { - flavor: SenderFlavor::Array(chan.new_sender(|_| unreachable!())), + flavor: SenderFlavor::Array(chan.new_sender(|_| todo!("but unreachable"))), }, ReceiverFlavor::List(chan) => Sender { flavor: SenderFlavor::List(chan.new_sender(|c| c.reconnect_senders())), }, ReceiverFlavor::Zero(chan) => Sender { - flavor: SenderFlavor::Zero(chan.new_sender(|_| unreachable!())), + flavor: SenderFlavor::Zero(chan.new_sender(|_| todo!("but unreachable"))), }, _ => unreachable!("This type cannot be built with at/never/tick"), } From 3aaf11510f1975fe0432a60de0c6fdd19b1b33cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Sun, 23 Jan 2022 18:59:42 +0100 Subject: [PATCH 6/9] Expand use statements --- crossbeam-channel/src/channel.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/crossbeam-channel/src/channel.rs b/crossbeam-channel/src/channel.rs index 8ae16eb93..9f0ea5d35 100644 --- a/crossbeam-channel/src/channel.rs +++ b/crossbeam-channel/src/channel.rs @@ -1519,10 +1519,19 @@ pub mod reconnectable { // The intended usage of this module is just to replace any `use crossbeam_channel::...` // with `use crossbeam_channel::reconnectable::...` and have everything work out of the box. - use super::*; - pub use crate::*; + // note: these re-exports omit + // at, tick, never: cannot be supported + // bounded: may be added later in this module + pub use crate::channel::{IntoIter, Iter, TryIter}; + pub use crate::err::{ReadyTimeoutError, SelectTimeoutError, TryReadyError, TrySelectError}; + pub use crate::err::{RecvError, RecvTimeoutError, TryRecvError}; + pub use crate::err::{SendError, SendTimeoutError, TrySendError}; + pub use crate::select::{Select, SelectedOperation}; + pub use crate::Sender; use super::Receiver as NormalReceiver; + use super::{ReceiverFlavor, SenderFlavor}; + use std::ops::Deref; /// An [unbounded](super::unbounded) channel whose receiver /// also is reconnectable. From e7c18722322074bd6a589cba7c685d408e263033 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Sun, 6 Feb 2022 17:26:45 +0100 Subject: [PATCH 7/9] Fix unstable todo macro use --- crossbeam-channel/src/channel.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crossbeam-channel/src/channel.rs b/crossbeam-channel/src/channel.rs index 9f0ea5d35..884daa697 100644 --- a/crossbeam-channel/src/channel.rs +++ b/crossbeam-channel/src/channel.rs @@ -1555,13 +1555,13 @@ pub mod reconnectable { pub fn new_sender(&self) -> Sender { match &self.0.flavor { ReceiverFlavor::Array(chan) => Sender { - flavor: SenderFlavor::Array(chan.new_sender(|_| todo!("but unreachable"))), + flavor: SenderFlavor::Array(chan.new_sender(|_| unimplemented!("but unreachable"))), }, ReceiverFlavor::List(chan) => Sender { flavor: SenderFlavor::List(chan.new_sender(|c| c.reconnect_senders())), }, ReceiverFlavor::Zero(chan) => Sender { - flavor: SenderFlavor::Zero(chan.new_sender(|_| todo!("but unreachable"))), + flavor: SenderFlavor::Zero(chan.new_sender(|_| unimplemented!("but unreachable"))), }, _ => unreachable!("This type cannot be built with at/never/tick"), } From f6eb65883bcd4b756dae1c529aa526ab706d72da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Sun, 6 Feb 2022 22:18:19 +0100 Subject: [PATCH 8/9] cargo fix --- crossbeam-channel/src/channel.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crossbeam-channel/src/channel.rs b/crossbeam-channel/src/channel.rs index 884daa697..b530f3f7c 100644 --- a/crossbeam-channel/src/channel.rs +++ b/crossbeam-channel/src/channel.rs @@ -3,7 +3,6 @@ use std::fmt; use std::iter::FusedIterator; use std::mem; -use std::ops::Deref; use std::panic::{RefUnwindSafe, UnwindSafe}; use std::sync::Arc; use std::time::{Duration, Instant}; From 1f73775a3016ff8db6cc71339f744a2681d1a6c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Wed, 9 Feb 2022 22:25:25 +0100 Subject: [PATCH 9/9] Cargo fmt --- crossbeam-channel/src/channel.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/crossbeam-channel/src/channel.rs b/crossbeam-channel/src/channel.rs index b530f3f7c..7fa8d1119 100644 --- a/crossbeam-channel/src/channel.rs +++ b/crossbeam-channel/src/channel.rs @@ -1554,13 +1554,17 @@ pub mod reconnectable { pub fn new_sender(&self) -> Sender { match &self.0.flavor { ReceiverFlavor::Array(chan) => Sender { - flavor: SenderFlavor::Array(chan.new_sender(|_| unimplemented!("but unreachable"))), + flavor: SenderFlavor::Array( + chan.new_sender(|_| unimplemented!("but unreachable")), + ), }, ReceiverFlavor::List(chan) => Sender { flavor: SenderFlavor::List(chan.new_sender(|c| c.reconnect_senders())), }, ReceiverFlavor::Zero(chan) => Sender { - flavor: SenderFlavor::Zero(chan.new_sender(|_| unimplemented!("but unreachable"))), + flavor: SenderFlavor::Zero( + chan.new_sender(|_| unimplemented!("but unreachable")), + ), }, _ => unreachable!("This type cannot be built with at/never/tick"), }