1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
//! Tag encoding configuration.
use ;
/// Tag encoding trait.
///
/// This trait normalizes discriminant encodings to a common type, `u32`.
/// The reason for this is that `SchemaRead` and `SchemaWrite` implementations
/// for enums need to match on explicit integer literals.
///
/// For example,
/// ```compile_fail
/// # use wincode::{SchemaRead, SchemaWrite, config::Config, io::Reader, ReadResult};
/// # use core::mem::MaybeUninit;
/// enum Foo {
/// Bar,
/// Baz,
/// }
///
/// unsafe impl<'de, C: Config> SchemaRead<'de, C> for Foo {
/// type Dst = Self;
///
/// fn read(reader: impl Reader<'de>, dst: &mut MaybeUninit<Self::Dst>) -> ReadResult<()> {
/// let tag = C::TagEncoding::get(reader)?;
/// // Cannot match a generic type with an integer literal.
/// match tag {
/// 0 => {}
/// 1 => {}
/// // ...
/// }
/// Ok(())
/// }
/// }
/// ```
///
/// It is not possible to match on a generic type with an integer literal.
/// This is because we have no way of telling the compiler that a trait
/// is represented by a closed set of integer types.
///
/// By normalizing the discriminant encoding to a common type, we can
/// get around this limitation.
///
/// ```
/// # use wincode::{
/// # SchemaRead, SchemaWrite,
/// # config::Config,
/// # io::Reader, ReadResult,
/// # tag_encoding::TagEncoding,
/// # error::invalid_tag_encoding,
/// # };
/// # use core::mem::MaybeUninit;
/// enum Foo {
/// Bar,
/// Baz,
/// }
///
/// unsafe impl<'de, C: Config> SchemaRead<'de, C> for Foo {
/// type Dst = Self;
///
/// fn read(reader: impl Reader<'de>, dst: &mut MaybeUninit<Self::Dst>) -> ReadResult<()> {
/// let tag = C::TagEncoding::try_into_u32(C::TagEncoding::get(reader)?)?;
/// // Now we can match on integer literals.
/// match tag {
/// 0 => {
/// // ...
/// }
/// 1 => {
/// // ...
/// }
/// _ => {
/// return Err(invalid_tag_encoding(tag as usize));
/// }
/// }
///
/// Ok(())
/// }
/// }
/// ```
///
/// A note on performance: in release builds, the generated assembly for this scheme
/// typically elides conversions to and from the intermediate `u32` type.
/// Because `TagEncoding` is ultimately monomorphized into concrete integer targets,
/// the result of `try_from`/`try_into` calls are known at compile time.
/// All reads, matches, and writes on enums in the crate (including derive macros)
/// use compile-time integer literals, and in practice it was observed that tags are
/// loaded at their original width and compared directly to immediates.