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();