Skip to content

Commit c324bc3

Browse files
committed
fix: update generic bounds on metadata getters
* update metadata fn docs BREAKING CHANGE: previous code that jumped through hoops to record metadata type A and retrieve type B, where A and B are metadata type trait impls for different table types, will no fail to compile.
1 parent 25cc8b6 commit c324bc3

File tree

7 files changed

+206
-27
lines changed

7 files changed

+206
-27
lines changed

src/edge_table.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,23 @@ impl<'a> EdgeTable<'a> {
136136
unsafe_tsk_column_access_and_map_into!(row.into().0, 0, self.num_rows(), self.table_, right)
137137
}
138138

139-
pub fn metadata<T: metadata::MetadataRoundtrip>(
139+
/// Retrieve decoded metadata for a `row`.
140+
///
141+
/// # Returns
142+
///
143+
/// * `Some(Ok(T))` if `row` is valid and decoding succeeded.
144+
/// * `Some(Err(_))` if `row` is not valid and decoding failed.
145+
/// * `None` if `row` is not valid.
146+
///
147+
/// # Errors
148+
///
149+
/// * [`TskitError::MetadataError`] if decoding fails.
150+
///
151+
/// # Examples.
152+
///
153+
/// The big-picture semantics are the same for all table types.
154+
/// See [`crate::IndividualTable::metadata`] for examples.
155+
pub fn metadata<T: metadata::EdgeMetadata>(
140156
&'a self,
141157
row: EdgeId,
142158
) -> Option<Result<T, TskitError>> {

src/individual_table.rs

Lines changed: 108 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -152,14 +152,13 @@ impl<'a> IndividualTable<'a> {
152152
///
153153
/// # Returns
154154
///
155-
/// The result type is `Option<T>`
156-
/// where `T`: [`tskit::metadata::IndividualMetadata`](crate::metadata::IndividualMetadata).
157-
/// `Some(T)` if there is metadata. `None` if the metadata field is empty for a given
158-
/// row.
155+
/// * `Some(Ok(T))` if `row` is valid and decoding succeeded.
156+
/// * `Some(Err(_))` if `row` is not valid and decoding failed.
157+
/// * `None` if `row` is not valid.
159158
///
160159
/// # Errors
161160
///
162-
/// * [`TskitError::IndexError`] if `row` is out of range.
161+
/// * [`TskitError::MetadataError`] if decoding fails.
163162
///
164163
/// # Examples
165164
///
@@ -190,15 +189,16 @@ impl<'a> IndividualTable<'a> {
190189
/// # let metadata = IndividualMetadata{x: 1};
191190
/// # assert!(tables.add_individual_with_metadata(0, None, None,
192191
/// # &metadata).is_ok());
193-
/// // We know the metadata are here, so we unwrap the Result and the Option
192+
/// // We know the metadata are here, so we unwrap the Option and the Result:
194193
/// let decoded = tables.individuals().metadata::<IndividualMetadata>(0.into()).unwrap().unwrap();
195194
/// assert_eq!(decoded.x, 1);
196195
/// # }
197196
/// ```
198197
///
199198
/// ## Checking for errors and absence of metadata
200199
///
201-
/// Handling both the possibility of error and optional metadata leads to some verbosity:
200+
/// The `Option<Result<_>>` return value allows all
201+
/// three return possibilities to be easily covered:
202202
///
203203
/// ```
204204
/// # #[cfg(feature = "derive")] {
@@ -213,22 +213,112 @@ impl<'a> IndividualTable<'a> {
213213
/// # assert!(tables
214214
/// # .add_individual_with_metadata(0, None, None, &metadata)
215215
/// # .is_ok());
216-
/// // First, check the Result.
217-
/// let decoded_option = match tables
218-
/// .individuals()
219-
/// .metadata::<IndividualMetadata>(0.into())
216+
/// match tables.individuals().metadata::<IndividualMetadata>(0.into())
220217
/// {
221-
/// Some(metadata_option) => metadata_option,
222-
/// None => panic!("expected metadata"),
218+
/// Some(Ok(metadata)) => assert_eq!(metadata.x, 1),
219+
/// Some(Err(_)) => panic!("got an error??"),
220+
/// None => panic!("Got None??"),
223221
/// };
224-
/// // Now, check the contents of the Option
225-
/// match decoded_option {
226-
/// Ok(metadata) => assert_eq!(metadata.x, 1),
227-
/// Err(e) => panic!("error decoding metadata: {:?}", e),
222+
/// # }
223+
/// ```
224+
///
225+
/// ## Attempting to use the wrong type.
226+
///
227+
/// Let's define a mutation metadata type with the exact same fields
228+
/// as our individual metadata defined above:
229+
///
230+
/// ```
231+
/// # #[cfg(feature = "derive")] {
232+
/// #[derive(serde::Serialize, serde::Deserialize, tskit::metadata::MutationMetadata)]
233+
/// #[serializer("serde_json")]
234+
/// struct MutationMetadata {
235+
/// x: i32,
236+
/// }
237+
/// # }
238+
/// ```
239+
///
240+
/// This type has the wrong trait bound and will cause compilation to fail:
241+
///
242+
#[cfg_attr(
243+
feature = "derive",
244+
doc = r##"
245+
```compile_fail
246+
# #[derive(serde::Serialize, serde::Deserialize, tskit::metadata::MutationMetadata)]
247+
# #[serializer("serde_json")]
248+
# struct MutationMetadata {
249+
# x: i32,
250+
# }
251+
# use tskit::TableAccess;
252+
# let mut tables = tskit::TableCollection::new(10.).unwrap();
253+
match tables.individuals().metadata::<MutationMetadata>(0.into())
254+
{
255+
Some(Ok(metadata)) => assert_eq!(metadata.x, 1),
256+
Some(Err(_)) => panic!("got an error??"),
257+
None => panic!("Got None??"),
258+
};
259+
```
260+
"##
261+
)]
262+
///
263+
/// ## Limitations: different type, same trait bound
264+
///
265+
/// Finally, let us consider a different struct that has identical
266+
/// fields to `IndividualMetadata` defined above and also implements
267+
/// the correct trait:
268+
///
269+
/// ```
270+
/// # #[cfg(feature = "derive")] {
271+
/// #[derive(serde::Serialize, serde::Deserialize, tskit::metadata::IndividualMetadata)]
272+
/// #[serializer("serde_json")]
273+
/// struct IndividualMetadataToo {
274+
/// x: i32,
228275
/// }
229276
/// # }
230277
/// ```
231-
pub fn metadata<T: metadata::MetadataRoundtrip>(
278+
///
279+
/// Let's walk through a detailed example:
280+
///
281+
/// ```
282+
/// # #[cfg(feature = "derive")] {
283+
/// # use tskit::TableAccess;
284+
/// # #[derive(serde::Serialize, serde::Deserialize, tskit::metadata::IndividualMetadata)]
285+
/// # #[serializer("serde_json")]
286+
/// # struct IndividualMetadata {
287+
/// # x: i32,
288+
/// # }
289+
/// # #[derive(serde::Serialize, serde::Deserialize, tskit::metadata::IndividualMetadata)]
290+
/// # #[serializer("serde_json")]
291+
/// # struct IndividualMetadataToo {
292+
/// # x: i32,
293+
/// # }
294+
/// // create a mutable table collection
295+
/// let mut tables = tskit::TableCollection::new(100.).unwrap();
296+
/// // Create some metadata based on our FIRST type
297+
/// let metadata = IndividualMetadata { x: 1 };
298+
/// // Add a row with our metadata
299+
/// assert!(tables.add_individual_with_metadata(0, None, None, &metadata).is_ok());
300+
/// // Trying to fetch using our SECOND type as the generic type works!
301+
/// match tables.individuals().metadata::<IndividualMetadataToo>(0.into())
302+
/// {
303+
/// Some(Ok(metadata)) => assert_eq!(metadata.x, 1),
304+
/// Some(Err(_)) => panic!("got an error??"),
305+
/// None => panic!("Got None??"),
306+
/// };
307+
/// # }
308+
/// ```
309+
///
310+
/// What is going on here?
311+
/// Both types satisfy the same trait bound ([`metadata::IndividualMetadata`])
312+
/// and their data fields look identical to `serde_json`.
313+
/// Thus, one is exchangeable for the other because they have the exact same
314+
/// *behavior*.
315+
///
316+
/// However, it is also true that this is (often/usually/always) not exactly what we want.
317+
/// We are experimenting with encapsulation APIs involving traits with
318+
/// [associated
319+
/// types](https://doc.rust-lang.org/book/ch19-03-advanced-traits.html#specifying-placeholder-types-in-trait-definitions-with-associated-types) to enforce at *compile time* that exactly one type (`struct/enum`, etc.) is a valid
320+
/// metadata type for a table.
321+
pub fn metadata<T: metadata::IndividualMetadata>(
232322
&'a self,
233323
row: IndividualId,
234324
) -> Option<Result<T, TskitError>> {

src/migration_table.rs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -165,14 +165,23 @@ impl<'a> MigrationTable<'a> {
165165
unsafe_tsk_column_access_and_map_into!(row.into().0, 0, self.num_rows(), self.table_, time)
166166
}
167167

168-
/// Return the metadata for a given row.
168+
/// Retrieve decoded metadata for a `row`.
169169
///
170170
/// # Returns
171171
///
172-
/// * `Some(Ok(metadata))` if `row` is valid and decoding succeeded
173-
/// * `Some(Err(_))` if `row` is valid and decoding failed.
172+
/// * `Some(Ok(T))` if `row` is valid and decoding succeeded.
173+
/// * `Some(Err(_))` if `row` is not valid and decoding failed.
174174
/// * `None` if `row` is not valid.
175-
pub fn metadata<T: metadata::MetadataRoundtrip>(
175+
///
176+
/// # Errors
177+
///
178+
/// * [`TskitError::MetadataError`] if decoding fails.
179+
///
180+
/// # Examples.
181+
///
182+
/// The big-picture semantics are the same for all table types.
183+
/// See [`crate::IndividualTable::metadata`] for examples.
184+
pub fn metadata<T: metadata::MigrationMetadata>(
176185
&'a self,
177186
row: MigrationId,
178187
) -> Option<Result<T, TskitError>> {

src/mutation_table.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,23 @@ impl<'a> MutationTable<'a> {
161161
)
162162
}
163163

164-
pub fn metadata<T: metadata::MetadataRoundtrip>(
164+
/// Retrieve decoded metadata for a `row`.
165+
///
166+
/// # Returns
167+
///
168+
/// * `Some(Ok(T))` if `row` is valid and decoding succeeded.
169+
/// * `Some(Err(_))` if `row` is not valid and decoding failed.
170+
/// * `None` if `row` is not valid.
171+
///
172+
/// # Errors
173+
///
174+
/// * [`TskitError::MetadataError`] if decoding fails.
175+
///
176+
/// # Examples.
177+
///
178+
/// The big-picture semantics are the same for all table types.
179+
/// See [`crate::IndividualTable::metadata`] for examples.
180+
pub fn metadata<T: metadata::MutationMetadata>(
165181
&'a self,
166182
row: MutationId,
167183
) -> Option<Result<T, TskitError>> {

src/node_table.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,23 @@ impl<'a> NodeTable<'a> {
327327
)
328328
}
329329

330-
pub fn metadata<T: metadata::MetadataRoundtrip>(
330+
/// Retrieve decoded metadata for a `row`.
331+
///
332+
/// # Returns
333+
///
334+
/// * `Some(Ok(T))` if `row` is valid and decoding succeeded.
335+
/// * `Some(Err(_))` if `row` is not valid and decoding failed.
336+
/// * `None` if `row` is not valid.
337+
///
338+
/// # Errors
339+
///
340+
/// * [`TskitError::MetadataError`] if decoding fails.
341+
///
342+
/// # Examples.
343+
///
344+
/// The big-picture semantics are the same for all table types.
345+
/// See [`crate::IndividualTable::metadata`] for examples.
346+
pub fn metadata<T: metadata::NodeMetadata>(
331347
&'a self,
332348
row: NodeId,
333349
) -> Option<Result<T, TskitError>> {

src/population_table.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,23 @@ impl<'a> PopulationTable<'a> {
8080
self.table_.num_rows.into()
8181
}
8282

83-
pub fn metadata<T: metadata::MetadataRoundtrip>(
83+
/// Retrieve decoded metadata for a `row`.
84+
///
85+
/// # Returns
86+
///
87+
/// * `Some(Ok(T))` if `row` is valid and decoding succeeded.
88+
/// * `Some(Err(_))` if `row` is not valid and decoding failed.
89+
/// * `None` if `row` is not valid.
90+
///
91+
/// # Errors
92+
///
93+
/// * [`TskitError::MetadataError`] if decoding fails.
94+
///
95+
/// # Examples.
96+
///
97+
/// The big-picture semantics are the same for all table types.
98+
/// See [`crate::IndividualTable::metadata`] for examples.
99+
pub fn metadata<T: metadata::PopulationMetadata>(
84100
&'a self,
85101
row: PopulationId,
86102
) -> Option<Result<T, TskitError>> {

src/site_table.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,23 @@ impl<'a> SiteTable<'a> {
112112
)
113113
}
114114

115-
pub fn metadata<T: metadata::MetadataRoundtrip>(
115+
/// Retrieve decoded metadata for a `row`.
116+
///
117+
/// # Returns
118+
///
119+
/// * `Some(Ok(T))` if `row` is valid and decoding succeeded.
120+
/// * `Some(Err(_))` if `row` is not valid and decoding failed.
121+
/// * `None` if `row` is not valid.
122+
///
123+
/// # Errors
124+
///
125+
/// * [`TskitError::MetadataError`] if decoding fails.
126+
///
127+
/// # Examples.
128+
///
129+
/// The big-picture semantics are the same for all table types.
130+
/// See [`crate::IndividualTable::metadata`] for examples.
131+
pub fn metadata<T: metadata::SiteMetadata>(
116132
&'a self,
117133
row: SiteId,
118134
) -> Option<Result<T, TskitError>> {

0 commit comments

Comments
 (0)