Skip to content

Commit 07876f9

Browse files
authored
doc: add book section on NodeDefaults (#474)
* update integration tests for clarity
1 parent abe8d7d commit 07876f9

File tree

2 files changed

+171
-12
lines changed

2 files changed

+171
-12
lines changed

book/src/table_collection_adding_rows.md

+91
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,94 @@ 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+
```

tests/book_table_collection.rs

+80-12
Original file line numberDiff line numberDiff line change
@@ -169,9 +169,15 @@ fn get_data_from_edge_table() {
169169
#[test]
170170
fn test_adding_node_table_row_with_defaults() {
171171
let mut tables = tskit::TableCollection::new(10.).unwrap();
172+
// ANCHOR: node_defaults
172173
let defaults = tskit::NodeDefaults::default();
174+
// ANCHOR_END: node_defaults
175+
// ANCHOR: add_node_defaults
173176
let node = tables.add_node_with_defaults(0.0, &defaults).unwrap();
177+
// ANCHOR_END: add_node_defaults
174178
assert_eq!(node, 0);
179+
180+
// ANCHOR: add_node_defaults_sample
175181
let node = tables
176182
.add_node_with_defaults(
177183
0.0,
@@ -184,6 +190,7 @@ fn test_adding_node_table_row_with_defaults() {
184190
},
185191
)
186192
.unwrap();
193+
// ANCHOR_END: add_node_defaults_sample
187194
assert!(tables.nodes().flags(node).unwrap().is_sample());
188195
}
189196

@@ -215,15 +222,17 @@ macro_rules! impl_node_metadata_traits {
215222
}
216223

217224
mod node_metadata {
218-
#[derive(serde::Serialize, serde::Deserialize)]
225+
#[derive(Debug, serde::Serialize, serde::Deserialize, Eq, PartialEq)]
226+
// ANCHOR: node_metadata
219227
pub struct NodeMetadata {
220228
pub value: i32,
221229
}
230+
// ANCHOR_END: node_metadata
222231
impl_node_metadata_traits!();
223232
}
224233

225234
mod node_metadata_clone {
226-
#[derive(Clone, serde::Serialize, serde::Deserialize)]
235+
#[derive(Debug, Eq, PartialEq, Clone, serde::Serialize, serde::Deserialize)]
227236
pub struct NodeMetadata {
228237
pub value: i32,
229238
}
@@ -234,10 +243,18 @@ mod node_metadata_clone {
234243
fn test_adding_node_table_row_with_defaults_and_metadata() {
235244
use node_metadata::NodeMetadata;
236245
let mut tables = tskit::TableCollection::new(10.0).unwrap();
246+
// ANCHOR: node_defaults_with_metadata
247+
248+
// Create a type alias for brevity
237249
type DefaultsWithMetadata = tskit::NodeDefaultsWithMetadata<NodeMetadata>;
250+
// Default metadata is None
238251
let defaults = DefaultsWithMetadata::default();
239-
let _ = tables.add_node_with_defaults(0.0, &defaults).unwrap();
240-
let _ = tables
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
241258
.add_node_with_defaults(
242259
0.0,
243260
&DefaultsWithMetadata {
@@ -247,32 +264,54 @@ fn test_adding_node_table_row_with_defaults_and_metadata() {
247264
},
248265
)
249266
.unwrap();
250-
let _ = tables
267+
268+
// Another row with metadata, different from the last.
269+
let n2 = tables
251270
.add_node_with_defaults(
252271
0.0,
253272
&DefaultsWithMetadata {
254-
population: 3.into(),
255-
metadata: Some(NodeMetadata { value: 42 }),
273+
population: 1.into(),
274+
metadata: Some(NodeMetadata { value: 1234 }),
256275
..defaults
257276
},
258277
)
259278
.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+
);
260297
}
261298

262299
#[test]
263300
fn test_adding_node_table_row_with_defaults_and_metadata_requiring_clone() {
264301
use node_metadata_clone::NodeMetadata;
265302
let mut tables = tskit::TableCollection::new(10.0).unwrap();
266303
type DefaultsWithMetadata = tskit::NodeDefaultsWithMetadata<NodeMetadata>;
304+
305+
// ANCHOR: node_defaults_with_some_metadata_default
267306
// What if there is default metadata for all rows?
268307
let defaults = DefaultsWithMetadata {
269308
metadata: Some(NodeMetadata { value: 42 }),
270309
..Default::default()
271310
};
311+
// ANCHOR_END: node_defaults_with_some_metadata_default
272312

273-
// We can scoop all non-metadata fields even though
274-
// type is not Copy/Clone
275-
let _ = tables
313+
// ANCHOR: node_defaults_with_some_metadata_default_add_first_row
314+
let n0 = tables
276315
.add_node_with_defaults(
277316
0.0,
278317
&DefaultsWithMetadata {
@@ -281,11 +320,21 @@ fn test_adding_node_table_row_with_defaults_and_metadata_requiring_clone() {
281320
},
282321
)
283322
.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+
);
284332

285333
// But now, we start to cause a problem:
286334
// If we don't clone here, our metadata type moves,
287335
// so our defaults are moved.
288-
let _ = tables
336+
// ANCHOR: node_defaults_with_some_metadata_default_add_second_row
337+
let n1 = tables
289338
.add_node_with_defaults(
290339
0.0,
291340
&DefaultsWithMetadata {
@@ -294,10 +343,20 @@ fn test_adding_node_table_row_with_defaults_and_metadata_requiring_clone() {
294343
},
295344
)
296345
.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+
);
297355

298356
// Now, we have a use-after-move error
299357
// if we hadn't cloned in the last step.
300-
let _ = tables
358+
// ANCHOR: node_defaults_with_some_metadata_default_add_third_row
359+
let n2 = tables
301360
.add_node_with_defaults(
302361
0.0,
303362
&DefaultsWithMetadata {
@@ -306,4 +365,13 @@ fn test_adding_node_table_row_with_defaults_and_metadata_requiring_clone() {
306365
},
307366
)
308367
.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+
);
309377
}

0 commit comments

Comments
 (0)