Skip to main content

hydro_lang/networking/
mod.rs

1//! Types for configuring network channels with serialization formats, transports, etc.
2
3use std::marker::PhantomData;
4
5use serde::Serialize;
6use serde::de::DeserializeOwned;
7
8use crate::live_collections::stream::networking::{deserialize_bincode, serialize_bincode};
9use crate::live_collections::stream::{NoOrder, Ordering, TotalOrder};
10use crate::location::cluster::{Consistency, EventualConsistency, NoConsistency};
11use crate::nondet::NonDet;
12
13#[sealed::sealed]
14trait SerKind<T: ?Sized> {
15    fn serialize_thunk(is_demux: bool) -> syn::Expr;
16
17    fn deserialize_thunk(tagged: Option<&syn::Type>) -> syn::Expr;
18
19    /// Whether this serialization backend leaves serialization to code outside of Hydro (see
20    /// [`Embedded`]). When `true`, [`Self::serialize_thunk`] and [`Self::deserialize_thunk`] are
21    /// never called; the raw element type flows across the channel unserialized.
22    fn is_embedded() -> bool {
23        false
24    }
25}
26
27/// Serialize items using the [`bincode`] crate.
28pub enum Bincode {}
29
30#[sealed::sealed]
31impl<T: Serialize + DeserializeOwned> SerKind<T> for Bincode {
32    fn serialize_thunk(is_demux: bool) -> syn::Expr {
33        serialize_bincode::<T>(is_demux)
34    }
35
36    fn deserialize_thunk(tagged: Option<&syn::Type>) -> syn::Expr {
37        deserialize_bincode::<T>(tagged)
38    }
39}
40
41/// Leaves serialization of items to code outside of Hydro. Every [`EMBEDDED`] channel uses it.
42///
43/// This serialization backend is only supported by the embedded deployment backend (it will panic
44/// on all other backends). The generated network channel exposes the raw element type `T` to the
45/// developer (rather than serialized bytes), so they can perform custom serialization logic outside
46/// of the Hydro program for that channel.
47pub enum Embedded {}
48
49#[sealed::sealed]
50impl<T> SerKind<T> for Embedded {
51    fn serialize_thunk(_is_demux: bool) -> syn::Expr {
52        unreachable!("embedded serialization does not use a serialize thunk")
53    }
54
55    fn deserialize_thunk(_tagged: Option<&syn::Type>) -> syn::Expr {
56        unreachable!("embedded serialization does not use a deserialize thunk")
57    }
58
59    fn is_embedded() -> bool {
60        true
61    }
62}
63
64/// An unconfigured serialization backend.
65pub enum NoSer {}
66
67/// A transport backend for network channels.
68#[sealed::sealed]
69#[diagnostic::on_unimplemented(
70    message = "this network config requires a failure policy",
71    note = "for example, `EMBEDDED.fail_stop()` delivers, per sender, an in-order prefix of the messages it sent"
72)]
73pub trait TransportKind {
74    /// The ordering guarantee provided by this transport.
75    type OrderingGuarantee: Ordering;
76
77    /// The consistency guarantee this transport can preserve for replicated outputs
78    /// (see [`NetworkFor::ConsistencyGuarantee`]).
79    type ConsistencyGuarantee: Consistency;
80
81    /// Returns the [`NetworkingInfo`] describing this transport's configuration.
82    fn networking_info() -> NetworkingInfo;
83}
84
85#[sealed::sealed]
86#[diagnostic::on_unimplemented(
87    message = "the TCP transport requires a failure policy",
88    note = "for example, `TCP.fail_stop()` stops delivering messages after a failed connection"
89)]
90/// A failure policy for TCP connections, determining how the transport handles
91/// connection failures and what ordering guarantees the output stream provides.
92pub trait TcpFailPolicy {
93    /// The ordering guarantee provided by this failure policy.
94    type OrderingGuarantee: Ordering;
95
96    /// The consistency guarantee this failure policy can preserve for replicated outputs
97    /// (see [`NetworkFor::ConsistencyGuarantee`]).
98    type ConsistencyGuarantee: Consistency;
99
100    /// Returns the [`TcpFault`] variant for this failure policy.
101    fn tcp_fault() -> TcpFault;
102}
103
104/// A TCP failure policy that stops sending messages after a failed connection.
105pub enum FailStop {}
106#[sealed::sealed]
107impl TcpFailPolicy for FailStop {
108    type OrderingGuarantee = TotalOrder;
109
110    // A failed connection stops *all* future deliveries to that recipient, which models the
111    // recipient as having failed. Consistency guarantees only apply to live members, so
112    // eventual consistency of replicated outputs is preserved.
113    type ConsistencyGuarantee = EventualConsistency;
114
115    fn tcp_fault() -> TcpFault {
116        TcpFault::FailStop
117    }
118}
119
120/// A failure policy that allows messages to be lost.
121pub enum Lossy {}
122#[sealed::sealed]
123impl TcpFailPolicy for Lossy {
124    type OrderingGuarantee = TotalOrder;
125
126    // A lossy channel can drop an arbitrary message for one recipient while continuing to
127    // deliver later messages, so replicated outputs can diverge across members forever.
128    type ConsistencyGuarantee = NoConsistency;
129
130    fn tcp_fault() -> TcpFault {
131        TcpFault::Lossy
132    }
133}
134
135/// A failure policy that treats dropped messages as indefinitely delayed.
136///
137/// Unlike [`Lossy`], this does not require a [`NonDet`] annotation because the output
138/// stream is always lower in the partial order than the ideal stream (dropped messages
139/// are modeled as infinite delays). The tradeoff is that the output has [`NoOrder`]
140/// guarantees, imposing stricter conditions on downstream consumers.
141///
142/// When using this mode in the Hydro simulator, you must call
143/// [`.test_safety_only()`](crate::sim::flow::SimFlow::test_safety_only): the simulator
144/// will not actually drop packets—it delays "dropped" messages until the end of the
145/// execution, which catches safety bugs but cannot test liveness.
146pub enum LossyDelayedForever {}
147#[sealed::sealed]
148impl TcpFailPolicy for LossyDelayedForever {
149    type OrderingGuarantee = NoOrder;
150
151    // Dropped messages are modeled as indefinitely delayed, so the output on each member is
152    // always a lower bound of the ideal stream that is eventually delivered in full; replicated
153    // outputs therefore remain eventually consistent.
154    type ConsistencyGuarantee = EventualConsistency;
155
156    fn tcp_fault() -> TcpFault {
157        TcpFault::LossyDelayedForever
158    }
159}
160
161#[sealed::sealed]
162#[diagnostic::on_unimplemented(
163    message = "the UDP transport requires a failure policy",
164    note = "for example, `UDP.lossy_delayed_forever()` treats dropped messages as indefinitely delayed"
165)]
166/// A failure policy for UDP channels, determining how the transport handles
167/// message loss. Because UDP provides no ordering guarantees, all policies
168/// produce [`NoOrder`] output streams, and there is no `fail_stop` option
169/// (UDP is connectionless, so there is no connection to fail).
170pub trait UdpFailPolicy {
171    /// The consistency guarantee this failure policy can preserve for replicated outputs
172    /// (see [`NetworkFor::ConsistencyGuarantee`]).
173    type ConsistencyGuarantee: Consistency;
174
175    /// Returns the [`UdpFault`] variant for this failure policy.
176    fn udp_fault() -> UdpFault;
177}
178
179#[sealed::sealed]
180impl UdpFailPolicy for Lossy {
181    // A lossy channel can drop an arbitrary message for one recipient while continuing to
182    // deliver later messages, so replicated outputs can diverge across members forever.
183    type ConsistencyGuarantee = NoConsistency;
184
185    fn udp_fault() -> UdpFault {
186        UdpFault::Lossy
187    }
188}
189
190#[sealed::sealed]
191impl UdpFailPolicy for LossyDelayedForever {
192    // Dropped messages are modeled as indefinitely delayed, so the output on each member is
193    // always a lower bound of the ideal stream that is eventually delivered in full; replicated
194    // outputs therefore remain eventually consistent.
195    type ConsistencyGuarantee = EventualConsistency;
196
197    fn udp_fault() -> UdpFault {
198        UdpFault::LossyDelayedForever
199    }
200}
201
202/// Send items across a length-delimited TCP channel.
203pub struct Tcp<F> {
204    _phantom: PhantomData<F>,
205}
206
207#[sealed::sealed]
208impl<F: TcpFailPolicy> TransportKind for Tcp<F> {
209    type OrderingGuarantee = F::OrderingGuarantee;
210
211    type ConsistencyGuarantee = F::ConsistencyGuarantee;
212
213    fn networking_info() -> NetworkingInfo {
214        NetworkingInfo::Tcp {
215            fault: F::tcp_fault(),
216        }
217    }
218}
219
220/// Items cross a channel that the host of an embedded program implements however it likes (a
221/// network connection, an in-memory queue, ...), keeping the guarantees of the policy `F`.
222pub struct EmbeddedTransport<F> {
223    _phantom: PhantomData<F>,
224}
225
226/// An [`EMBEDDED`] channel names no transport, so its [`NetworkingInfo`] is just the guarantees
227/// its received stream is declared with, which the host's channel must keep.
228fn embedded_info<K: TransportKind>() -> NetworkingInfo {
229    NetworkingInfo::Embedded {
230        ordering: K::OrderingGuarantee::ORDERING_KIND,
231        consistency: K::ConsistencyGuarantee::consistency(),
232    }
233}
234
235/// For each sender, an in-order prefix of the messages it sent.
236#[sealed::sealed]
237impl TransportKind for EmbeddedTransport<FailStop> {
238    type OrderingGuarantee = TotalOrder;
239
240    type ConsistencyGuarantee = EventualConsistency;
241
242    fn networking_info() -> NetworkingInfo {
243        embedded_info::<Self>()
244    }
245}
246
247/// Each sender's messages in order, any of which may be dropped.
248#[sealed::sealed]
249impl TransportKind for EmbeddedTransport<Lossy> {
250    type OrderingGuarantee = TotalOrder;
251
252    type ConsistencyGuarantee = NoConsistency;
253
254    fn networking_info() -> NetworkingInfo {
255        embedded_info::<Self>()
256    }
257}
258
259/// Messages in any order; a message never delivered counts as delayed forever.
260#[sealed::sealed]
261impl TransportKind for EmbeddedTransport<LossyDelayedForever> {
262    type OrderingGuarantee = NoOrder;
263
264    type ConsistencyGuarantee = EventualConsistency;
265
266    fn networking_info() -> NetworkingInfo {
267        embedded_info::<Self>()
268    }
269}
270
271/// Send items across a UDP channel, which does not guarantee delivery or ordering.
272pub struct Udp<F> {
273    _phantom: PhantomData<F>,
274}
275
276#[sealed::sealed]
277impl<F: UdpFailPolicy> TransportKind for Udp<F> {
278    type OrderingGuarantee = NoOrder;
279
280    type ConsistencyGuarantee = F::ConsistencyGuarantee;
281
282    fn networking_info() -> NetworkingInfo {
283        NetworkingInfo::Udp {
284            fault: F::udp_fault(),
285        }
286    }
287}
288
289/// A networking backend implementation that supports items of type `T`.
290#[sealed::sealed]
291pub trait NetworkFor<T: ?Sized> {
292    /// The ordering guarantee provided by this network configuration.
293    /// When combined with an input stream's ordering `O`, the output ordering
294    /// will be `<O as MinOrder<Self::OrderingGuarantee>>::Min`.
295    type OrderingGuarantee: Ordering;
296
297    /// The consistency guarantee this network configuration can preserve when the same data is
298    /// replicated to several recipients (e.g. via
299    /// [`Stream::broadcast_closed`](crate::live_collections::stream::Stream::broadcast_closed)).
300    ///
301    /// Failure policies that guarantee each recipient eventually observes the full stream of
302    /// sent messages, or that model failures as the recipient stopping entirely (such as
303    /// `fail_stop` or `lossy_delayed_forever`), preserve
304    /// [`EventualConsistency`]. Plain `lossy`
305    /// channels can silently drop individual messages for some recipients while others receive
306    /// them, so replicated outputs can permanently diverge and only
307    /// [`NoConsistency`] is guaranteed.
308    type ConsistencyGuarantee: Consistency;
309
310    /// Generates serialization logic for sending `T`.
311    fn serialize_thunk(is_demux: bool) -> syn::Expr;
312
313    /// Generates deserialization logic for receiving `T`.
314    fn deserialize_thunk(tagged: Option<&syn::Type>) -> syn::Expr;
315
316    /// Whether this network channel leaves serialization to code outside of Hydro (an
317    /// [`EMBEDDED`] channel). When `true`, [`Self::serialize_thunk`] and
318    /// [`Self::deserialize_thunk`] are never called; the raw element type flows across the
319    /// channel unserialized.
320    fn is_embedded() -> bool {
321        false
322    }
323
324    /// Returns the optional name of the network channel.
325    fn name(&self) -> Option<&str>;
326
327    /// Returns the [`NetworkingInfo`] describing this network channel's transport and fault model.
328    fn networking_info() -> NetworkingInfo;
329}
330
331/// The fault model for a TCP connection.
332#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize)]
333pub enum TcpFault {
334    /// Stops sending messages after a failed connection.
335    FailStop,
336    /// Messages may be lost (e.g. due to network partitions).
337    Lossy,
338    /// Dropped messages are treated as indefinitely delayed with no ordering guarantee.
339    LossyDelayedForever,
340}
341
342/// The fault model for a UDP channel.
343///
344/// UDP is connectionless and never guarantees delivery, so there is no
345/// `FailStop` variant — messages can always be dropped.
346#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize)]
347pub enum UdpFault {
348    /// Messages may be lost (e.g. due to network partitions or congestion).
349    Lossy,
350    /// Dropped messages are treated as indefinitely delayed.
351    LossyDelayedForever,
352}
353
354/// Describes the networking configuration for a network channel at the IR level.
355#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize)]
356pub enum NetworkingInfo {
357    /// A TCP-based network channel with a specific fault model.
358    Tcp {
359        /// The fault model for this TCP connection.
360        fault: TcpFault,
361    },
362    /// A UDP-based network channel with a specific fault model.
363    Udp {
364        /// The fault model for this UDP channel.
365        fault: UdpFault,
366    },
367    /// A channel implemented by the host of an embedded program ([`EMBEDDED`]), described by
368    /// the guarantees its received stream is declared with, which the host's channel must keep.
369    Embedded {
370        /// The order the channel must deliver each sender's messages in: `TotalOrder` (the order
371        /// they were sent) or `NoOrder` (any).
372        ordering: crate::compile::ir::StreamOrder,
373        /// Whether the channel may lose a message and deliver later ones as if nothing were
374        /// missing (`NoConsistency`), or must not (`EventualConsistency`).
375        consistency: crate::location::dynamic::ClusterConsistency,
376    },
377}
378
379/// A network channel configuration with `T` as transport backend and `S` as the serialization
380/// backend.
381pub struct NetworkingConfig<Tr: ?Sized, S: ?Sized, Name = ()> {
382    name: Option<Name>,
383    _phantom: (PhantomData<Tr>, PhantomData<S>),
384}
385
386impl<Tr: ?Sized, S: ?Sized> NetworkingConfig<Tr, S> {
387    /// Names the network channel and enables stable communication across multiple service versions.
388    pub fn name(self, name: impl Into<String>) -> NetworkingConfig<Tr, S, String> {
389        NetworkingConfig {
390            name: Some(name.into()),
391            _phantom: (PhantomData, PhantomData),
392        }
393    }
394}
395
396impl<Tr: ?Sized, N> NetworkingConfig<Tr, NoSer, N> {
397    /// Configures the network channel to use [`bincode`] to serialize items.
398    pub const fn bincode(mut self) -> NetworkingConfig<Tr, Bincode, N> {
399        let taken_name = self.name.take();
400        std::mem::forget(self); // nothing else is stored
401        NetworkingConfig {
402            name: taken_name,
403            _phantom: (PhantomData, PhantomData),
404        }
405    }
406}
407
408impl<S: ?Sized> NetworkingConfig<Tcp<()>, S> {
409    /// Configures the TCP transport to stop sending messages after a failed connection.
410    ///
411    /// Note that the Hydro simulator will not simulate connection failures that impact the
412    /// *liveness* of a program. If an output assertion depends on a `fail_stop` channel
413    /// making progress, that channel will not experience a failure that would cause the test to
414    /// block indefinitely. However, any *safety* issues caused by connection failures will still
415    /// be caught, such as a race condition between a failed connection and some other message.
416    #[must_use]
417    pub const fn fail_stop(self) -> NetworkingConfig<Tcp<FailStop>, S> {
418        NetworkingConfig {
419            name: self.name,
420            _phantom: (PhantomData, PhantomData),
421        }
422    }
423
424    /// Configures the TCP transport to allow messages to be lost.
425    ///
426    /// This is appropriate for networks where messages may be dropped, such as when
427    /// running under a Maelstrom partition nemesis. Unlike `fail_stop`, which guarantees
428    /// a prefix of messages is delivered, `lossy` makes no such guarantee.
429    ///
430    /// # Non-Determinism
431    /// A lossy TCP channel will non-deterministically drop messages during execution.
432    #[must_use]
433    pub const fn lossy(self, nondet: NonDet) -> NetworkingConfig<Tcp<Lossy>, S> {
434        let _ = nondet;
435        NetworkingConfig {
436            name: self.name,
437            _phantom: (PhantomData, PhantomData),
438        }
439    }
440
441    /// Configures the TCP transport to treat dropped messages as indefinitely delayed.
442    ///
443    /// This is appropriate for networks where messages may be dropped, such as when
444    /// running under a Maelstrom partition nemesis. Unlike [`Self::lossy`], this does
445    /// *not* require a [`NonDet`] annotation because the output is always lower in the
446    /// partial order than the ideal stream. However, the output stream will have
447    /// [`NoOrder`] guarantees, imposing stricter conditions on downstream consumers.
448    ///
449    /// Unlike [`Self::lossy`], this mode can easily be simulated in exhaustive mode
450    /// without running into fairness issues.
451    ///
452    /// When using this mode in the Hydro simulator, you must call
453    /// [`.test_safety_only()`](crate::sim::flow::SimFlow::test_safety_only) to opt in:
454    /// the simulator will not actually drop packets—it delays "dropped" messages until
455    /// the end of the execution, which catches safety bugs but cannot test liveness.
456    #[must_use]
457    pub const fn lossy_delayed_forever(self) -> NetworkingConfig<Tcp<LossyDelayedForever>, S> {
458        NetworkingConfig {
459            name: self.name,
460            _phantom: (PhantomData, PhantomData),
461        }
462    }
463}
464
465impl<S: ?Sized> NetworkingConfig<EmbeddedTransport<()>, S> {
466    /// Requires the host's channel to stop delivering messages after a failure, like
467    /// [`TCP.fail_stop()`](NetworkingConfig::fail_stop): for each sender, an in-order prefix of the
468    /// messages it sent.
469    #[must_use]
470    pub const fn fail_stop(self) -> NetworkingConfig<EmbeddedTransport<FailStop>, S> {
471        NetworkingConfig {
472            name: self.name,
473            _phantom: (PhantomData, PhantomData),
474        }
475    }
476
477    /// Allows the host's channel to lose messages while delivering each sender's messages in
478    /// order, like [`TCP.lossy()`](NetworkingConfig::lossy).
479    ///
480    /// # Non-Determinism
481    /// A lossy channel will non-deterministically drop messages during execution.
482    #[must_use]
483    pub const fn lossy(self, nondet: NonDet) -> NetworkingConfig<EmbeddedTransport<Lossy>, S> {
484        let _ = nondet;
485        NetworkingConfig {
486            name: self.name,
487            _phantom: (PhantomData, PhantomData),
488        }
489    }
490
491    /// Allows the host's channel to deliver messages in any order and treats a message that is
492    /// never delivered as indefinitely delayed, like
493    /// [`TCP.lossy_delayed_forever()`](NetworkingConfig::lossy_delayed_forever).
494    #[must_use]
495    pub const fn lossy_delayed_forever(
496        self,
497    ) -> NetworkingConfig<EmbeddedTransport<LossyDelayedForever>, S> {
498        NetworkingConfig {
499            name: self.name,
500            _phantom: (PhantomData, PhantomData),
501        }
502    }
503}
504
505impl<S: ?Sized> NetworkingConfig<Udp<()>, S> {
506    /// Configures the UDP transport to allow messages to be lost.
507    ///
508    /// UDP never guarantees delivery or ordering, so unlike TCP there is no `fail_stop`
509    /// policy — messages may always be dropped and the output stream always has
510    /// [`NoOrder`] guarantees.
511    ///
512    /// # Non-Determinism
513    /// A lossy UDP channel will non-deterministically drop messages during execution.
514    #[must_use]
515    pub const fn lossy(self, nondet: NonDet) -> NetworkingConfig<Udp<Lossy>, S> {
516        let _ = nondet;
517        NetworkingConfig {
518            name: self.name,
519            _phantom: (PhantomData, PhantomData),
520        }
521    }
522
523    /// Configures the UDP transport to treat dropped messages as indefinitely delayed.
524    ///
525    /// UDP never guarantees delivery or ordering, so unlike TCP there is no `fail_stop`
526    /// policy. Unlike [`Self::lossy`], this does *not* require a [`NonDet`] annotation
527    /// because the output is always lower in the partial order than the ideal stream
528    /// (dropped messages are modeled as infinite delays). The output stream has
529    /// [`NoOrder`] guarantees, imposing stricter conditions on downstream consumers.
530    ///
531    /// Unlike [`Self::lossy`], this mode can easily be simulated in exhaustive mode
532    /// without running into fairness issues.
533    ///
534    /// When using this mode in the Hydro simulator, you must call
535    /// [`.test_safety_only()`](crate::sim::flow::SimFlow::test_safety_only) to opt in:
536    /// the simulator will not actually drop packets—it delays "dropped" messages until
537    /// the end of the execution, which catches safety bugs but cannot test liveness.
538    #[must_use]
539    pub const fn lossy_delayed_forever(self) -> NetworkingConfig<Udp<LossyDelayedForever>, S> {
540        NetworkingConfig {
541            name: self.name,
542            _phantom: (PhantomData, PhantomData),
543        }
544    }
545}
546
547#[sealed::sealed]
548impl<Tr, S, T: ?Sized> NetworkFor<T> for NetworkingConfig<Tr, S>
549where
550    Tr: ?Sized + TransportKind,
551    S: ?Sized + SerKind<T>,
552{
553    type OrderingGuarantee = Tr::OrderingGuarantee;
554
555    type ConsistencyGuarantee = Tr::ConsistencyGuarantee;
556
557    fn serialize_thunk(is_demux: bool) -> syn::Expr {
558        S::serialize_thunk(is_demux)
559    }
560
561    fn deserialize_thunk(tagged: Option<&syn::Type>) -> syn::Expr {
562        S::deserialize_thunk(tagged)
563    }
564
565    fn is_embedded() -> bool {
566        S::is_embedded()
567    }
568
569    fn name(&self) -> Option<&str> {
570        None
571    }
572
573    fn networking_info() -> NetworkingInfo {
574        Tr::networking_info()
575    }
576}
577
578#[sealed::sealed]
579impl<Tr, S, T: ?Sized> NetworkFor<T> for NetworkingConfig<Tr, S, String>
580where
581    Tr: ?Sized + TransportKind,
582    S: ?Sized + SerKind<T>,
583{
584    type OrderingGuarantee = Tr::OrderingGuarantee;
585
586    type ConsistencyGuarantee = Tr::ConsistencyGuarantee;
587
588    fn serialize_thunk(is_demux: bool) -> syn::Expr {
589        S::serialize_thunk(is_demux)
590    }
591
592    fn deserialize_thunk(tagged: Option<&syn::Type>) -> syn::Expr {
593        S::deserialize_thunk(tagged)
594    }
595
596    fn is_embedded() -> bool {
597        S::is_embedded()
598    }
599
600    fn name(&self) -> Option<&str> {
601        self.name.as_deref()
602    }
603
604    fn networking_info() -> NetworkingInfo {
605        Tr::networking_info()
606    }
607}
608
609/// A network channel that uses length-delimited TCP for transport.
610pub const TCP: NetworkingConfig<Tcp<()>, NoSer> = NetworkingConfig {
611    name: None,
612    _phantom: (PhantomData, PhantomData),
613};
614
615/// A network channel implemented by the host of an embedded program, which chooses how messages
616/// travel: a network connection, an in-memory queue, or anything else that delivers them with the
617/// guarantees of the chosen failure policy (for example `EMBEDDED.fail_stop().name("messages")`).
618///
619/// Items cross the channel unserialized: the generated channel ends carry the raw element type,
620/// and the host encodes it if its transport needs to. The program names only the guarantees, not
621/// a transport, so it is only supported by the embedded deployment backend and the Hydro
622/// simulator (which carries the raw values in memory); other backends use [`TCP`] or [`UDP`].
623pub const EMBEDDED: NetworkingConfig<EmbeddedTransport<()>, Embedded> = NetworkingConfig {
624    name: None,
625    _phantom: (PhantomData, PhantomData),
626};
627
628/// A network channel that uses UDP for transport.
629///
630/// Unlike [`TCP`], UDP does not guarantee delivery or ordering, so output streams
631/// always have [`NoOrder`] guarantees. Because UDP is connectionless, there is no
632/// `fail_stop` policy; only [`lossy`](NetworkingConfig::lossy) and
633/// [`lossy_delayed_forever`](NetworkingConfig::lossy_delayed_forever) are available.
634///
635/// # Availability
636/// UDP is **not yet available** in "deploy" deployment mode (via Hydro Deploy,
637/// including Docker and ECS deployments); attempting to deploy a UDP channel there
638/// will panic at compile time. Both UDP modes are available for embedded
639/// deployments (the only production deployment option) and Maelstrom testing. In
640/// the Hydro simulator, only `lossy_delayed_forever` is supported, and it requires
641/// [`.test_safety_only()`](crate::sim::flow::SimFlow::test_safety_only): the
642/// simulator will not actually drop packets—it delays "dropped" messages until the
643/// end of the execution, which catches safety bugs but cannot test liveness.
644pub const UDP: NetworkingConfig<Udp<()>, NoSer> = NetworkingConfig {
645    name: None,
646    _phantom: (PhantomData, PhantomData),
647};