Skip to content

Commit 4900163

Browse files
authored
doc: book section on node defaults (#491)
1 parent a24c618 commit 4900163

File tree

2 files changed

+302
-0
lines changed

2 files changed

+302
-0
lines changed

book/src/table_collection_adding_rows.md

+92
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,95 @@ Again, we can take advantage of being able to pass in any type that is `Into<_>`
2424
```
2525

2626
See the [API docs](https://docs.rs/tskit) for more details and examples.
27+
28+
### Adding nodes using default values
29+
30+
This section is more advanced and may be skipped during a first read.
31+
32+
For some tables it may be common to input the same values over and over for some fields when adding rows.
33+
Let's take a look at how to use default values when adding rows to a node table.
34+
35+
Default instances of `NodeDefaults` contain default values for the `flags`, `individual`, and `population` fields:
36+
37+
```rust, noplaygound, ignore
38+
{{#include ../../tests/book_table_collection.rs:node_defaults}}
39+
```
40+
41+
Add a node with these values and a given birth time:
42+
43+
```rust, noplaygound, ignore
44+
{{#include ../../tests/book_table_collection.rs:add_node_defaults}}
45+
```
46+
47+
We can use struct update syntax to create a new node marked as a sample while re-using our other defaults:
48+
49+
```rust, noplaygound, ignore
50+
{{#include ../../tests/book_table_collection.rs:add_node_defaults_sample}}
51+
```
52+
53+
See the [`NodeDefaults`](https://docs.rs/tskit/latest/tskit/struct.NodeDefaults.html) section of the API reference for more.
54+
55+
#### Metadata
56+
57+
[Metadata](metadata.md#Metadata) can complicate the picture a bit:
58+
59+
* Metadata types are defined by the client and are thus a generic in the `tskit` API.
60+
* We do not want to impose too many trait bounds on the client-defined types.
61+
* Metadata is optional on a per-row basis for any given table.
62+
63+
[`NodeDefaultsWithMetadata`](https://docs.rs/tskit/latest/tskit/struct.NodeDefaultsWithMetadata.html) handles the case where rows may or may not have metadata.
64+
The metadata type is generic with trait bound [`tskit::NodeMetadata`](https://docs.rs/tskit/latest/tskit/metadata/trait.NodeMetadata.html).
65+
Because metadata are optional per-row, any metadata defaults are stored as an [`Option`](https://doc.rust-lang.org/std/option/).
66+
67+
For the following examples, this will be our metadata type:
68+
69+
```rust, noplaygound, ignore
70+
{{#include ../../tests/book_table_collection.rs:node_metadata}}
71+
```
72+
73+
##### Case 1: no default metadata
74+
75+
A common use case is that the metadata differs for every row.
76+
For this case, it makes sense for the default value to be the `None` variant of the `Option`.
77+
78+
This case is straightforward:
79+
80+
```rust, noplaygound, ignore
81+
{{#include ../../tests/book_table_collection.rs:node_defaults_with_metadata}}
82+
```
83+
84+
##### Case 2: default metadata
85+
86+
TL;DR:
87+
88+
* If table row defaults *include* metadata, you can run into use-after-move issues.
89+
Fortunately, the compiler will catch this as an error.
90+
* The solution is for your metadata type to implement `Clone`.
91+
92+
Consider the following case:
93+
94+
```rust, noplaygound, ignore
95+
{{#include ../../tests/book_table_collection.rs:node_defaults_with_some_metadata_default}}
96+
```
97+
98+
Imagine that the first row we add uses different metadata but all the other default values:
99+
100+
```rust, noplaygound, ignore
101+
{{#include ../../tests/book_table_collection.rs:node_defaults_with_some_metadata_default_add_first_row}}
102+
```
103+
104+
Nothing interesting has happened.
105+
However, let's take a look at what we need to do if our next row uses a non-default `population` field and the default metadata:
106+
107+
```rust, noplaygound, ignore
108+
{{#include ../../tests/book_table_collection.rs:node_defaults_with_some_metadata_default_add_second_row}}
109+
```
110+
111+
Note the call to `..defaults.clone()`.
112+
(For that call to compile, `NodeMetadata` must implement `Clone`!.)
113+
Without that, our `defaults` instance would have *moved*, leading to a move-after-use compiler error when we add a third row:
114+
115+
```rust, noplaygound, ignore
116+
{{#include ../../tests/book_table_collection.rs:node_defaults_with_some_metadata_default_add_third_row}}
117+
```
118+

tests/book_table_collection.rs

+210
Original file line numberDiff line numberDiff line change
@@ -165,3 +165,213 @@ fn get_data_from_edge_table() {
165165
.check_integrity(tskit::TableIntegrityCheckFlags::default())
166166
.is_ok());
167167
}
168+
169+
#[test]
170+
fn test_adding_node_table_row_with_defaults() {
171+
let mut tables = tskit::TableCollection::new(10.).unwrap();
172+
// ANCHOR: node_defaults
173+
let defaults = tskit::NodeDefaults::default();
174+
// ANCHOR_END: node_defaults
175+
// ANCHOR: add_node_defaults
176+
let node = tables.add_node_with_defaults(0.0, &defaults).unwrap();
177+
// ANCHOR_END: add_node_defaults
178+
assert_eq!(node, 0);
179+
180+
// ANCHOR: add_node_defaults_sample
181+
let node = tables
182+
.add_node_with_defaults(
183+
0.0,
184+
// Create a new, temporary defaults instance
185+
&tskit::NodeDefaults {
186+
// Mark the new node as a sample
187+
flags: tskit::NodeFlags::new_sample(),
188+
// Use remaining values from our current defaults
189+
..defaults
190+
},
191+
)
192+
.unwrap();
193+
// ANCHOR_END: add_node_defaults_sample
194+
assert!(tables.nodes().flags(node).unwrap().is_sample());
195+
}
196+
197+
macro_rules! impl_node_metadata_traits {
198+
() => {
199+
impl tskit::metadata::MetadataRoundtrip for NodeMetadata {
200+
fn encode(&self) -> Result<Vec<u8>, tskit::metadata::MetadataError> {
201+
match serde_json::to_string(self) {
202+
Ok(x) => Ok(x.as_bytes().to_vec()),
203+
Err(e) => {
204+
Err(::tskit::metadata::MetadataError::RoundtripError { value: Box::new(e) })
205+
}
206+
}
207+
}
208+
fn decode(md: &[u8]) -> Result<Self, tskit::metadata::MetadataError>
209+
where
210+
Self: Sized,
211+
{
212+
match serde_json::from_slice(md) {
213+
Ok(v) => Ok(v),
214+
Err(e) => {
215+
Err(::tskit::metadata::MetadataError::RoundtripError { value: Box::new(e) })
216+
}
217+
}
218+
}
219+
}
220+
impl tskit::metadata::NodeMetadata for NodeMetadata {}
221+
};
222+
}
223+
224+
mod node_metadata {
225+
#[derive(Debug, serde::Serialize, serde::Deserialize, Eq, PartialEq)]
226+
// ANCHOR: node_metadata
227+
pub struct NodeMetadata {
228+
pub value: i32,
229+
}
230+
// ANCHOR_END: node_metadata
231+
impl_node_metadata_traits!();
232+
}
233+
234+
mod node_metadata_clone {
235+
#[derive(Debug, Eq, PartialEq, Clone, serde::Serialize, serde::Deserialize)]
236+
pub struct NodeMetadata {
237+
pub value: i32,
238+
}
239+
impl_node_metadata_traits!();
240+
}
241+
242+
#[test]
243+
fn test_adding_node_table_row_with_defaults_and_metadata() {
244+
use node_metadata::NodeMetadata;
245+
let mut tables = tskit::TableCollection::new(10.0).unwrap();
246+
// ANCHOR: node_defaults_with_metadata
247+
248+
// Create a type alias for brevity
249+
type DefaultsWithMetadata = tskit::NodeDefaultsWithMetadata<NodeMetadata>;
250+
// Default metadata is None
251+
let defaults = DefaultsWithMetadata::default();
252+
253+
// A row with no metadata
254+
let n0 = tables.add_node_with_defaults(0.0, &defaults).unwrap();
255+
256+
// A row with metadata
257+
let n1 = tables
258+
.add_node_with_defaults(
259+
0.0,
260+
&DefaultsWithMetadata {
261+
population: 3.into(),
262+
metadata: Some(NodeMetadata { value: 42 }),
263+
..defaults
264+
},
265+
)
266+
.unwrap();
267+
268+
// Another row with metadata, different from the last.
269+
let n2 = tables
270+
.add_node_with_defaults(
271+
0.0,
272+
&DefaultsWithMetadata {
273+
population: 1.into(),
274+
metadata: Some(NodeMetadata { value: 1234 }),
275+
..defaults
276+
},
277+
)
278+
.unwrap();
279+
// ANCHOR_END: node_defaults_with_metadata
280+
assert!(tables.nodes().metadata::<NodeMetadata>(n0).is_none());
281+
assert_eq!(
282+
tables
283+
.nodes()
284+
.metadata::<NodeMetadata>(n1)
285+
.unwrap()
286+
.unwrap(),
287+
NodeMetadata { value: 42 }
288+
);
289+
assert_eq!(
290+
tables
291+
.nodes()
292+
.metadata::<NodeMetadata>(n2)
293+
.unwrap()
294+
.unwrap(),
295+
NodeMetadata { value: 1234 }
296+
);
297+
}
298+
299+
#[test]
300+
fn test_adding_node_table_row_with_defaults_and_metadata_requiring_clone() {
301+
use node_metadata_clone::NodeMetadata;
302+
let mut tables = tskit::TableCollection::new(10.0).unwrap();
303+
type DefaultsWithMetadata = tskit::NodeDefaultsWithMetadata<NodeMetadata>;
304+
305+
// ANCHOR: node_defaults_with_some_metadata_default
306+
// What if there is default metadata for all rows?
307+
let defaults = DefaultsWithMetadata {
308+
metadata: Some(NodeMetadata { value: 42 }),
309+
..Default::default()
310+
};
311+
// ANCHOR_END: node_defaults_with_some_metadata_default
312+
313+
// ANCHOR: node_defaults_with_some_metadata_default_add_first_row
314+
let n0 = tables
315+
.add_node_with_defaults(
316+
0.0,
317+
&DefaultsWithMetadata {
318+
metadata: Some(NodeMetadata { value: 2 * 42 }),
319+
..defaults
320+
},
321+
)
322+
.unwrap();
323+
// ANCHOR_END: node_defaults_with_some_metadata_default_add_first_row
324+
assert_eq!(
325+
tables
326+
.nodes()
327+
.metadata::<NodeMetadata>(n0)
328+
.unwrap()
329+
.unwrap(),
330+
NodeMetadata { value: 2 * 42 }
331+
);
332+
333+
// But now, we start to cause a problem:
334+
// If we don't clone here, our metadata type moves,
335+
// so our defaults are moved.
336+
// ANCHOR: node_defaults_with_some_metadata_default_add_second_row
337+
let n1 = tables
338+
.add_node_with_defaults(
339+
0.0,
340+
&DefaultsWithMetadata {
341+
population: 6.into(),
342+
..defaults.clone()
343+
},
344+
)
345+
.unwrap();
346+
// ANCHOR_END: node_defaults_with_some_metadata_default_add_second_row
347+
assert_eq!(
348+
tables
349+
.nodes()
350+
.metadata::<NodeMetadata>(n1)
351+
.unwrap()
352+
.unwrap(),
353+
NodeMetadata { value: 42 }
354+
);
355+
356+
// Now, we have a use-after-move error
357+
// if we hadn't cloned in the last step.
358+
// ANCHOR: node_defaults_with_some_metadata_default_add_third_row
359+
let n2 = tables
360+
.add_node_with_defaults(
361+
0.0,
362+
&DefaultsWithMetadata {
363+
individual: 7.into(),
364+
..defaults
365+
},
366+
)
367+
.unwrap();
368+
// ANCHOR_END: node_defaults_with_some_metadata_default_add_third_row
369+
assert_eq!(
370+
tables
371+
.nodes()
372+
.metadata::<NodeMetadata>(n2)
373+
.unwrap()
374+
.unwrap(),
375+
NodeMetadata { value: 42 }
376+
);
377+
}

0 commit comments

Comments
 (0)