Adding rows to tables
- For each table type (node, edge., etc.), we have a function to add a row.
- We can only add rows to tables in mutable
TableCollection
instances.
For example, to add a node:
let mut tables = tskit::TableCollection::new(100.0).unwrap();
if let Ok(node_id) = tables.add_node(
0, // Node flags
tskit::Time::from(0.0), // Birth time
tskit::PopulationId::NULL, // Population id
tskit::IndividualId::NULL, // Individual id
) {
assert_eq!(node_id, 0);
}
We see from the if let
pattern that functions adding rows return
Result
.
In general, errors only occur when the C back-end fails to allocate memory
to expand the table columns.
If we add a row with invalid data, no error is returned!
To catch such errors, we must explicitly check table integrity (see below).
Again, we can take advantage of being able to pass in any type that is Into<_>
the required newtype:
let node_id = tables.add_node(0, 0.0, -1, -1).unwrap();
See the API docs for more details and examples.
Adding nodes using default values
This section is more advanced and may be skipped during a first read.
For some tables it may be common to input the same values over and over for some fields when adding rows. Let's take a look at how to use default values when adding rows to a node table.
Default instances of NodeDefaults
contain default values for the flags
, individual
, and population
fields:
let defaults = tskit::NodeDefaults::default();
Add a node with these values and a given birth time:
let node = tables.add_node_with_defaults(0.0, &defaults).unwrap();
We can use struct update syntax to create a new node marked as a sample while re-using our other defaults:
let node = tables
.add_node_with_defaults(
0.0,
// Create a new, temporary defaults instance
&tskit::NodeDefaults {
// Mark the new node as a sample
flags: tskit::NodeFlags::new_sample(),
// Use remaining values from our current defaults
..defaults
},
)
.unwrap();
See the NodeDefaults
section of the API reference for more.
Metadata
Metadata can complicate the picture a bit:
- Metadata types are defined by the client and are thus a generic in the
tskit
API. - We do not want to impose too many trait bounds on the client-defined types.
- Metadata is optional on a per-row basis for any given table.
NodeDefaultsWithMetadata
handles the case where rows may or may not have metadata.
The metadata type is generic with trait bound tskit::NodeMetadata
.
Because metadata are optional per-row, any metadata defaults are stored as an Option
.
For the following examples, this will be our metadata type:
pub struct NodeMetadata {
pub value: i32,
}
Case 1: no default metadata
A common use case is that the metadata differs for every row.
For this case, it makes sense for the default value to be the None
variant of the Option
.
This case is straightforward:
// Create a type alias for brevity
type DefaultsWithMetadata = tskit::NodeDefaultsWithMetadata<NodeMetadata>;
// Default metadata is None
let defaults = DefaultsWithMetadata::default();
// A row with no metadata
let n0 = tables.add_node_with_defaults(0.0, &defaults).unwrap();
// A row with metadata
let n1 = tables
.add_node_with_defaults(
0.0,
&DefaultsWithMetadata {
population: 3.into(),
metadata: Some(NodeMetadata { value: 42 }),
..defaults
},
)
.unwrap();
// Another row with metadata, different from the last.
let n2 = tables
.add_node_with_defaults(
0.0,
&DefaultsWithMetadata {
population: 1.into(),
metadata: Some(NodeMetadata { value: 1234 }),
..defaults
},
)
.unwrap();
Case 2: default metadata
TL;DR:
- If table row defaults include metadata, you can run into use-after-move issues. Fortunately, the compiler will catch this as an error.
- The solution is for your metadata type to implement
Clone
.
Consider the following case:
// What if there is default metadata for all rows?
let defaults = DefaultsWithMetadata {
metadata: Some(NodeMetadata { value: 42 }),
..Default::default()
};
Imagine that the first row we add uses different metadata but all the other default values:
let n0 = tables
.add_node_with_defaults(
0.0,
&DefaultsWithMetadata {
metadata: Some(NodeMetadata { value: 2 * 42 }),
..defaults
},
)
.unwrap();
Nothing interesting has happened.
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:
let n1 = tables
.add_node_with_defaults(
0.0,
&DefaultsWithMetadata {
population: 6.into(),
..defaults.clone()
},
)
.unwrap();
Note the call to ..defaults.clone()
.
(For that call to compile, NodeMetadata
must implement Clone
!.)
Without that, our defaults
instance would have moved, leading to a move-after-use compiler error when we add a third row:
let n2 = tables
.add_node_with_defaults(
0.0,
&DefaultsWithMetadata {
individual: 7.into(),
..defaults
},
)
.unwrap();