diff --git a/src/_macros.rs b/src/_macros.rs index 811045819..ed8d77817 100644 --- a/src/_macros.rs +++ b/src/_macros.rs @@ -142,18 +142,13 @@ macro_rules! metadata_to_vector { macro_rules! decode_metadata_row { ($T: ty, $buffer: expr) => { - match $buffer { - None => Ok(None), - Some(v) => Ok(Some(<$T as $crate::metadata::MetadataRoundtrip>::decode( - &v, - )?)), - } + <$T as $crate::metadata::MetadataRoundtrip>::decode($buffer) }; } macro_rules! table_row_decode_metadata { ($owner: ident, $table: ident, $pos: ident) => { - metadata_to_vector!($owner, $table, $pos).ok()?.map(|x| x) + metadata_to_vector!($owner, $table, $pos) }; } diff --git a/src/edge_table.rs b/src/edge_table.rs index 699ad72f5..eb6902656 100644 --- a/src/edge_table.rs +++ b/src/edge_table.rs @@ -130,10 +130,10 @@ impl<'a> EdgeTable<'a> { pub fn metadata( &'a self, row: EdgeId, - ) -> Result, TskitError> { + ) -> Option> { let table_ref = self.table_; let buffer = metadata_to_vector!(self, table_ref, row.0)?; - decode_metadata_row!(T, buffer) + Some(decode_metadata_row!(T, buffer).map_err(|e| e.into())) } /// Return an iterator over rows of the table. @@ -200,12 +200,13 @@ build_owned_table_type!( /// let rowid = edges.add_row_with_metadata(0., 1., 5, 10, &metadata).unwrap(); /// assert_eq!(rowid, 0); /// - /// if let Some(decoded) = edges.metadata::(rowid).unwrap() { - /// assert_eq!(decoded.value, 42); - /// } else { - /// panic!("hmm...we expected some metadata!"); + /// match edges.metadata::(rowid) { + /// // rowid is in range, decoding succeeded + /// Some(Ok(decoded)) => assert_eq!(decoded.value, 42), + /// // rowid is in range, decoding failed + /// Some(Err(e)) => panic!("error decoding metadata: {:?}", e), + /// None => panic!("row id out of range") /// } - /// /// # } /// ``` => OwnedEdgeTable, diff --git a/src/individual_table.rs b/src/individual_table.rs index 80b7d4763..06f6c6996 100644 --- a/src/individual_table.rs +++ b/src/individual_table.rs @@ -224,23 +224,23 @@ impl<'a> IndividualTable<'a> { /// .individuals() /// .metadata::(0.into()) /// { - /// Ok(metadata_option) => metadata_option, - /// Err(e) => panic!("error: {:?}", e), + /// Some(metadata_option) => metadata_option, + /// None => panic!("expected metadata"), /// }; /// // Now, check the contents of the Option /// match decoded_option { - /// Some(metadata) => assert_eq!(metadata.x, 1), - /// None => panic!("we expected Some(metadata)?"), + /// Ok(metadata) => assert_eq!(metadata.x, 1), + /// Err(e) => panic!("error decoding metadata: {:?}", e), /// } /// # } /// ``` pub fn metadata( &'a self, row: IndividualId, - ) -> Result, TskitError> { + ) -> Option> { let table_ref = self.table_; let buffer = metadata_to_vector!(self, table_ref, row.0)?; - decode_metadata_row!(T, buffer) + Some(decode_metadata_row!(T, buffer).map_err(|e| e.into())) } /// Return an iterator over rows of the table. @@ -288,6 +288,7 @@ build_owned_table_type!( /// An example with metadata. /// This requires the cargo feature `"derive"` for `tskit`. /// + /// /// ``` /// # #[cfg(any(feature="doc", feature="derive"))] { /// use tskit::OwnedIndividualTable; @@ -307,10 +308,12 @@ build_owned_table_type!( /// let rowid = individuals.add_row_with_metadata(0, None, None, &metadata).unwrap(); /// assert_eq!(rowid, 0); /// - /// if let Some(decoded) = individuals.metadata::(rowid).unwrap() { - /// assert_eq!(decoded.value, 42); - /// } else { - /// panic!("hmm...we expected some metadata!"); + /// match individuals.metadata::(rowid) { + /// // rowid is in range, decoding succeeded + /// Some(Ok(decoded)) => assert_eq!(decoded.value, 42), + /// // rowid is in range, decoding failed + /// Some(Err(e)) => panic!("error decoding metadata: {:?}", e), + /// None => panic!("row id out of range") /// } /// /// # } diff --git a/src/metadata.rs b/src/metadata.rs index e489f12f5..0e211b1da 100644 --- a/src/metadata.rs +++ b/src/metadata.rs @@ -164,7 +164,7 @@ //! into `Python` via the `tskit` `Python API`. use crate::bindings::{tsk_id_t, tsk_size_t}; -use crate::{SizeType, TskitError}; +use crate::SizeType; use thiserror::Error; #[cfg(feature = "derive")] @@ -257,25 +257,17 @@ pub(crate) fn char_column_to_slice( row: tsk_id_t, num_rows: tsk_size_t, column_length: tsk_size_t, -) -> Result, crate::TskitError> { - let row = match tsk_size_t::try_from(row) { - Ok(r) => r, - Err(_) => return Err(TskitError::IndexError), +) -> Option<&[u8]> { + let row = match tsk_size_t::try_from(row).ok() { + Some(r) if r < num_rows => r, + _ => return None, }; - if row >= num_rows { - return Err(crate::TskitError::IndexError {}); - } if column_length == 0 { - return Ok(None); + return None; } - let row_isize = match isize::try_from(row) { - Ok(r) => r, - Err(_) => { - return Err(TskitError::RangeError(format!( - "could not convert u64 value {} to isize", - stringify!(row) - ))) - } + let row_isize = match isize::try_from(row).ok() { + Some(x) => x, + None => return None, }; let start = unsafe { *column_offset.offset(row_isize) }; let stop = if (row as tsk_size_t) < num_rows { @@ -284,32 +276,22 @@ pub(crate) fn char_column_to_slice( column_length }; if start >= stop { - return Ok(None); + return None; } if column_length == 0 { - return Ok(None); + return None; } - let istart = match isize::try_from(start) { - Ok(v) => v, - Err(_) => { - return Err(TskitError::RangeError(format!( - "cauld not convert value {} to isize", - stringify!(i) - ))); - } + let istart = match isize::try_from(start).ok() { + Some(v) => v, + None => return None, }; - let ustop = match usize::try_from(stop) { - Ok(v) => v, - Err(_) => { - return Err(TskitError::RangeError(format!( - "cauld not convert value {} to usize", - stringify!(i) - ))); - } + let ustop = match usize::try_from(stop).ok() { + Some(v) => v, + None => return None, }; - Ok(Some(unsafe { + Some(unsafe { std::slice::from_raw_parts(column.offset(istart) as *const u8, ustop - istart as usize) - })) + }) } #[cfg(test)] diff --git a/src/migration_table.rs b/src/migration_table.rs index 0e62da604..96aaa8ea2 100644 --- a/src/migration_table.rs +++ b/src/migration_table.rs @@ -176,10 +176,10 @@ impl<'a> MigrationTable<'a> { pub fn metadata( &'a self, row: MigrationId, - ) -> Result, TskitError> { + ) -> Option> { let table_ref = self.table_; let buffer = metadata_to_vector!(self, table_ref, row.0)?; - decode_metadata_row!(T, buffer) + Some(decode_metadata_row!(T, buffer).map_err(|e| e.into())) } /// Return an iterator over rows of the table. @@ -242,10 +242,12 @@ build_owned_table_type!( /// let rowid = migrations.add_row_with_metadata((0., 1.), 1, (0, 1), 10.3, &metadata).unwrap(); /// assert_eq!(rowid, 0); /// - /// if let Some(decoded) = migrations.metadata::(rowid).unwrap() { - /// assert_eq!(decoded.value, 42); - /// } else { - /// panic!("hmm...we expected some metadata!"); + /// match migrations.metadata::(rowid) { + /// // rowid is in range, decoding succeeded + /// Some(Ok(decoded)) => assert_eq!(decoded.value, 42), + /// // rowid is in range, decoding failed + /// Some(Err(e)) => panic!("error decoding metadata: {:?}", e), + /// None => panic!("row id out of range") /// } /// /// # } diff --git a/src/mutation_table.rs b/src/mutation_table.rs index 8c33b8475..d9d4f6539 100644 --- a/src/mutation_table.rs +++ b/src/mutation_table.rs @@ -30,17 +30,25 @@ impl PartialEq for MutationTableRow { } fn make_mutation_table_row(table: &MutationTable, pos: tsk_id_t) -> Option { - let table_ref = table.table_; - Some(MutationTableRow { - id: pos.into(), - site: table.site(pos).ok()?, - node: table.node(pos).ok()?, - parent: table.parent(pos).ok()?, - time: table.time(pos).ok()?, - derived_state: table.derived_state(pos).ok()?.map(|s| s.to_vec()), - metadata: table_row_decode_metadata!(table, table_ref, pos).map(|m| m.to_vec()), - }) + let index = ll_bindings::tsk_size_t::try_from(pos).ok()?; + match index { + i if i < table.num_rows() => { + let table_ref = table.table_; + let derived_state = table.derived_state(pos).map(|s| s.to_vec()); + Some(MutationTableRow { + id: pos.into(), + site: table.site(pos).ok()?, + node: table.node(pos).ok()?, + parent: table.parent(pos).ok()?, + time: table.time(pos).ok()?, + derived_state, + metadata: table_row_decode_metadata!(table, table_ref, pos).map(|m| m.to_vec()), + }) + } + _ => None, + } } + pub(crate) type MutationTableRefIterator<'a> = crate::table_iterator::TableIterator<&'a MutationTable<'a>>; pub(crate) type MutationTableIterator<'a> = crate::table_iterator::TableIterator>; @@ -143,10 +151,7 @@ impl<'a> MutationTable<'a> { /// /// Will return [``IndexError``](crate::TskitError::IndexError) /// if ``row`` is out of range. - pub fn derived_state>( - &'a self, - row: M, - ) -> Result, TskitError> { + pub fn derived_state>(&'a self, row: M) -> Option<&[u8]> { metadata::char_column_to_slice( self, self.table_.derived_state, @@ -160,10 +165,10 @@ impl<'a> MutationTable<'a> { pub fn metadata( &'a self, row: MutationId, - ) -> Result, TskitError> { + ) -> Option> { let table_ref = self.table_; let buffer = metadata_to_vector!(self, table_ref, row.0)?; - decode_metadata_row!(T, buffer) + Some(decode_metadata_row!(T, buffer).map_err(|e| e.into())) } /// Return an iterator over rows of the table. @@ -226,12 +231,13 @@ build_owned_table_type!( /// let rowid = mutations.add_row_with_metadata(0, 1, 5, 10.0, None, &metadata).unwrap(); /// assert_eq!(rowid, 0); /// -/// if let Some(decoded) = mutations.metadata::(rowid).unwrap() { -/// assert_eq!(decoded.value, 42); -/// } else { -/// panic!("hmm...we expected some metadata!"); +/// match mutations.metadata::(rowid) { +/// // rowid is in range, decoding succeeded +/// Some(Ok(decoded)) => assert_eq!(decoded.value, 42), +/// // rowid is in range, decoding failed +/// Some(Err(e)) => panic!("error decoding metadata: {:?}", e), +/// None => panic!("row id out of range") /// } -/// /// # } /// ``` => OwnedMutationTable, diff --git a/src/node_table.rs b/src/node_table.rs index eb901d22d..55e126fff 100644 --- a/src/node_table.rs +++ b/src/node_table.rs @@ -191,10 +191,10 @@ impl<'a> NodeTable<'a> { pub fn metadata( &'a self, row: NodeId, - ) -> Result, TskitError> { + ) -> Option> { let table_ref = self.table_; let buffer = metadata_to_vector!(self, table_ref, row.0)?; - decode_metadata_row!(T, buffer) + Some(decode_metadata_row!(T, buffer).map_err(|e| e.into())) } /// Return an iterator over rows of the table. @@ -285,10 +285,12 @@ build_owned_table_type!( /// let rowid = nodes.add_row_with_metadata(0, 1., -1, -1, &metadata).unwrap(); /// assert_eq!(rowid, 0); /// - /// if let Some(decoded) = nodes.metadata::(rowid).unwrap() { - /// assert_eq!(decoded.value, 42); - /// } else { - /// panic!("hmm...we expected some metadata!"); + /// match nodes.metadata::(rowid) { + /// // rowid is in range, decoding succeeded + /// Some(Ok(decoded)) => assert_eq!(decoded.value, 42), + /// // rowid is in range, decoding failed + /// Some(Err(e)) => panic!("error decoding metadata: {:?}", e), + /// None => panic!("row id out of range") /// } /// /// # } diff --git a/src/population_table.rs b/src/population_table.rs index c81787c62..e63bc1aa6 100644 --- a/src/population_table.rs +++ b/src/population_table.rs @@ -21,10 +21,18 @@ impl PartialEq for PopulationTableRow { fn make_population_table_row(table: &PopulationTable, pos: tsk_id_t) -> Option { let table_ref = table.table_; - Some(PopulationTableRow { - id: pos.into(), - metadata: table_row_decode_metadata!(table, table_ref, pos).map(|m| m.to_vec()), - }) + let index = ll_bindings::tsk_size_t::try_from(pos).ok()?; + + match index { + i if i < table.num_rows() => { + let metadata = table_row_decode_metadata!(table, table_ref, pos).map(|s| s.to_vec()); + Some(PopulationTableRow { + id: pos.into(), + metadata, + }) + } + _ => None, + } } pub(crate) type PopulationTableRefIterator<'a> = @@ -75,10 +83,10 @@ impl<'a> PopulationTable<'a> { pub fn metadata( &'a self, row: PopulationId, - ) -> Result, TskitError> { + ) -> Option> { let table_ref = self.table_; let buffer = metadata_to_vector!(self, table_ref, row.0)?; - decode_metadata_row!(T, buffer) + Some(decode_metadata_row!(T, buffer).map_err(TskitError::from)) } /// Return an iterator over rows of the table. @@ -144,12 +152,13 @@ build_owned_table_type!( /// let rowid = populations.add_row_with_metadata(&metadata).unwrap(); /// assert_eq!(rowid, 0); /// -/// if let Some(decoded) = populations.metadata::(rowid).unwrap() { -/// assert_eq!(&decoded.name, "YRB"); -/// } else { -/// panic!("hmm...we expected some metadata!"); +/// match populations.metadata::(rowid) { +/// // rowid is in range, decoding succeeded +/// Some(Ok(decoded)) => assert_eq!(&decoded.name, "YRB"), +/// // rowid is in range, decoding failed +/// Some(Err(e)) => panic!("error decoding metadata: {:?}", e), +/// None => panic!("row id out of range") /// } -/// /// # } /// ``` => OwnedPopulationTable, diff --git a/src/site_table.rs b/src/site_table.rs index 826ad5e00..99c5ac8ff 100644 --- a/src/site_table.rs +++ b/src/site_table.rs @@ -26,10 +26,11 @@ impl PartialEq for SiteTableRow { fn make_site_table_row(table: &SiteTable, pos: tsk_id_t) -> Option { let table_ref = table.table_; + let ancestral_state = table.ancestral_state(pos).map(|s| s.to_vec()); Some(SiteTableRow { id: pos.into(), position: table.position(pos).ok()?, - ancestral_state: table.ancestral_state(pos).ok()?.map(|s| s.to_vec()), + ancestral_state, metadata: table_row_decode_metadata!(table, table_ref, pos).map(|m| m.to_vec()), }) } @@ -99,7 +100,7 @@ impl<'a> SiteTable<'a> { /// /// Will return [``IndexError``](crate::TskitError::IndexError) /// if ``row`` is out of range. - pub fn ancestral_state>(&'a self, row: S) -> Result, TskitError> { + pub fn ancestral_state>(&'a self, row: S) -> Option<&[u8]> { crate::metadata::char_column_to_slice( self, self.table_.ancestral_state, @@ -113,10 +114,10 @@ impl<'a> SiteTable<'a> { pub fn metadata( &'a self, row: SiteId, - ) -> Result, TskitError> { + ) -> Option> { let table_ref = self.table_; let buffer = metadata_to_vector!(self, table_ref, row.0)?; - decode_metadata_row!(T, buffer) + Some(decode_metadata_row!(T, buffer).map_err(TskitError::from)) } /// Return an iterator over rows of the table. @@ -179,12 +180,13 @@ build_owned_table_type!( /// let rowid = sites.add_row_with_metadata(0., None, &metadata).unwrap(); /// assert_eq!(rowid, 0); /// - /// if let Some(decoded) = sites.metadata::(rowid).unwrap() { - /// assert_eq!(decoded.value, 42); - /// } else { - /// panic!("hmm...we expected some metadata!"); + /// match sites.metadata::(rowid) { + /// // rowid is in range, decoding succeeded + /// Some(Ok(decoded)) => assert_eq!(decoded.value, 42), + /// // rowid is in range, decoding failed + /// Some(Err(e)) => panic!("error decoding metadata: {:?}", e), + /// None => panic!("row id out of range") /// } - /// /// # } /// ``` => OwnedSiteTable, diff --git a/src/table_collection.rs b/src/table_collection.rs index 224c2f1be..54712c972 100644 --- a/src/table_collection.rs +++ b/src/table_collection.rs @@ -1451,16 +1451,16 @@ mod test { assert!(close_enough(sites.position(1).unwrap(), 0.5)); assert!(close_enough(sites.position(2).unwrap(), 0.9)); - match sites.ancestral_state(0).unwrap() { + match sites.ancestral_state(0) { Some(astate) => assert_eq!(astate, b"Eggnog"), None => panic!(), }; - if sites.ancestral_state(1).unwrap().is_some() { + if sites.ancestral_state(1).is_some() { panic!() } - match sites.ancestral_state(2).unwrap() { + match sites.ancestral_state(2) { Some(astate) => assert_eq!(astate, longer_metadata.as_bytes()), None => panic!(), }; @@ -1526,16 +1526,13 @@ mod test { assert_eq!(mutations.parent(0).unwrap(), MutationId::NULL); assert_eq!(mutations.parent(1).unwrap(), MutationId::NULL); assert_eq!(mutations.parent(2).unwrap(), MutationId::NULL); - assert_eq!(mutations.derived_state(0).unwrap().unwrap(), b"pajamas"); + assert_eq!(mutations.derived_state(0).unwrap(), b"pajamas"); - if mutations.derived_state(1).unwrap().is_some() { + if mutations.derived_state(1).is_some() { panic!() } - assert_eq!( - mutations.derived_state(2).unwrap().unwrap(), - b"more pajamas" - ); + assert_eq!(mutations.derived_state(2).unwrap(), b"more pajamas"); let mut nmuts = 0; for (i, row) in tables.mutations().iter().enumerate() { @@ -1644,6 +1641,7 @@ mod test { match tables .mutations() .metadata::((i as tsk_id_t).into()) + .transpose() .unwrap() { Some(x) => { @@ -1797,9 +1795,11 @@ mod test_bad_metadata { tables .add_mutation_with_metadata(0, 0, MutationId::NULL, 0.0, None, &md) .unwrap(); - if tables.mutations().metadata::(0.into()).is_ok() { - panic!("expected an error!!"); - } + assert!(tables + .mutations() + .metadata::(0.into()) + .transpose() + .is_err()); } } @@ -1837,15 +1837,13 @@ mod test_adding_node { tables.nodes().individual(row_id).unwrap(), IndividualId::NULL, ); - assert!(tables - .nodes() - .metadata::(row_id) - .unwrap() - .is_none()); + assert!(tables.nodes().metadata::(row_id).is_none()); // We are playing a dangerous game here, // in that we do not have any populations. // Fortunately, we are range-checked everywhere. + println!("{}", tables.nodes().population(row_id).unwrap()); + println!("{}", tables.populations().num_rows()); assert!(tables .populations() .row(tables.nodes().population(row_id).unwrap()) @@ -1884,7 +1882,11 @@ mod test_adding_node { Err(_) => panic!("unexpected Err"), }; assert_eq!( - tables.nodes().metadata::(row_id).unwrap(), + tables + .nodes() + .metadata::(row_id) + .transpose() + .unwrap(), Some(metadata[mi]) ); } @@ -1971,6 +1973,7 @@ mod test_adding_individual { tables .individuals() .metadata::(row_id) + .transpose() .unwrap(), Some(metadata[mi]) ); @@ -2029,7 +2032,11 @@ mod test_adding_edge { Err(_) => panic!("unexpected Err"), }; assert_eq!( - tables.edges().metadata::(edge_id).unwrap(), + tables + .edges() + .metadata::(edge_id) + .transpose() + .unwrap(), Some(metadata[mi]) ); } @@ -2069,7 +2076,6 @@ mod test_adding_mutation { if tables .mutations() .metadata::(row) - .unwrap() .is_some() { panic!("expected None"); @@ -2091,6 +2097,7 @@ mod test_adding_mutation { tables .mutations() .metadata::(mut_id) + .transpose() .unwrap(), Some(metadata[mi]) ); @@ -2124,7 +2131,6 @@ mod test_adding_site { assert!(tables .sites() .metadata::(site_id) - .unwrap() .is_none()); let row = tables.sites().row(site_id).unwrap(); @@ -2144,7 +2150,11 @@ mod test_adding_site { Err(_) => panic!("unexpected Err"), }; assert_eq!( - tables.sites().metadata::(site_id).unwrap(), + tables + .sites() + .metadata::(site_id) + .transpose() + .unwrap(), Some(metadata[mi]) ); } @@ -2182,7 +2192,6 @@ mod test_adding_population { assert!(tables .populations() .metadata::(pop_id) - .unwrap() .is_none()); for row in tables.populations_iter() { @@ -2193,6 +2202,8 @@ mod test_adding_population { assert!(row.metadata.is_none()); } + println!("{} {}", pop_id, tables.populations().num_rows()); + assert!( tables.populations().row(pop_id).unwrap() == tables.populations().row(pop_id).unwrap() ); @@ -2208,6 +2219,7 @@ mod test_adding_population { tables .populations() .metadata::(pop_id) + .transpose() .unwrap() == Some(GenericMetadata::default()) ); @@ -2237,7 +2249,6 @@ mod test_adding_migrations { assert!(tables .migrations() .metadata::(mig_id) - .unwrap() .is_none()); } diff --git a/tests/experimental.rs b/tests/experimental.rs index 6ee00e631..1b60666d6 100644 --- a/tests/experimental.rs +++ b/tests/experimental.rs @@ -12,7 +12,7 @@ mod experimental_features { // * So hopefully we can start there. trait MetadataRetrieval { type Metadata: tskit::metadata::MetadataRoundtrip; - fn metadata(&self, r: impl Into) -> Result, tskit::TskitError>; + fn metadata(&self, r: impl Into) -> Option>; } // Specific traits cover the various row id types @@ -20,9 +20,8 @@ mod experimental_features { fn mutation_metadata( &self, row: impl Into, - ) -> Result< - Option<>::Metadata>, - tskit::TskitError, + ) -> Option< + Result<>::Metadata, tskit::TskitError>, > where >::Metadata: @@ -39,9 +38,8 @@ mod experimental_features { fn mutation_metadata( &self, row: impl Into, - ) -> Result< - Option<>::Metadata>, - tskit::TskitError, + ) -> Option< + Result<>::Metadata, tskit::TskitError>, > { self.metadata(row) } @@ -51,9 +49,8 @@ mod experimental_features { fn individual_metadata( &self, row: impl Into, - ) -> Result< - Option<>::Metadata>, - tskit::TskitError, + ) -> Option< + Result<>::Metadata, tskit::TskitError>, > where >::Metadata: @@ -69,9 +66,8 @@ mod experimental_features { fn individual_metadata( &self, row: impl Into, - ) -> Result< - Option<>::Metadata>, - tskit::TskitError, + ) -> Option< + Result<>::Metadata, tskit::TskitError>, > { self.metadata(row) } @@ -86,7 +82,7 @@ mod experimental_features { fn get_mutation_metadata>( &self, row: M, - ) -> Result, tskit::TskitError>; + ) -> Option>; } #[derive(serde::Serialize, serde::Deserialize, tskit::metadata::MutationMetadata)] @@ -132,7 +128,7 @@ mod experimental_features { fn get_mutation_metadata>( &self, row: M, - ) -> Result, tskit::TskitError> { + ) -> Option> { self.mutations().metadata::(row.into()) } } @@ -142,7 +138,7 @@ mod experimental_features { fn metadata( &self, row: impl Into, - ) -> Result, tskit::TskitError> { + ) -> Option> { self.mutations() .metadata::(row.into()) } @@ -153,7 +149,7 @@ mod experimental_features { fn metadata( &self, row: impl Into, - ) -> Result, tskit::TskitError> { + ) -> Option> { self.individuals() .metadata::(row.into()) } @@ -241,7 +237,7 @@ mod experimental_features_refined { fn get_mutation_metadata>( &self, row: M, - ) -> Result, tskit::TskitError> { + ) -> Option> { self.as_tables() .mutations() .metadata::(row.into())