Skip to content

Commit 7f3e060

Browse files
committed
refactor: TableColumn trait for column access
replaces concrete types added in #726, #730
1 parent e6c1662 commit 7f3e060

File tree

8 files changed

+160
-75
lines changed

8 files changed

+160
-75
lines changed

.github/workflows/tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ jobs:
8383
strategy:
8484
matrix:
8585
rust:
86-
- 1.71.0
86+
- 1.75.0
8787
steps:
8888
- uses: actions/[email protected]
8989
- uses: dtolnay/rust-toolchain@v1

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ description = "rust interface to tskit"
88
license = "MIT"
99
homepage = "https://github.com/tskit-dev/tskit-rust"
1010
repository = "https://github.com/tskit-dev/tskit-rust"
11-
rust-version = "1.71.0"
11+
rust-version = "1.75.0"
1212

1313
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
1414
[lints.rust]

src/edge_table.rs

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -358,20 +358,37 @@ impl EdgeTable {
358358
/// Get the child column as a slice of the underlying integer type
359359
=> child, child_slice_raw, ll_bindings::tsk_id_t);
360360

361-
pub fn parent_column(&self) -> crate::EdgeTableColumn<crate::NodeId> {
362-
crate::EdgeTableColumn::new(self.parent_slice())
361+
/// Table column with ergonomic indexing.
362+
///
363+
/// # Examples
364+
///
365+
/// ```rust
366+
/// use tskit::TableColumn;
367+
/// let mut edges = tskit::EdgeTable::new().unwrap();
368+
/// // left, right, parent, child
369+
/// let edge: tskit::EdgeId = edges.add_row(0., 10., 1, 0).unwrap();
370+
/// let p = edges.parent_column();
371+
/// assert_eq!(p[edge], 1);
372+
/// assert_eq!(p.get_with_id(edge), Some(&tskit::NodeId::from(1)));
373+
/// assert!(p.get_with_id(tskit::EdgeId::NULL).is_none())
374+
/// ```
375+
pub fn parent_column(&self) -> impl crate::TableColumn<EdgeId, NodeId> + '_ {
376+
crate::table_column::OpaqueTableColumn(self.parent_slice())
363377
}
364378

365-
pub fn child_column(&self) -> crate::EdgeTableColumn<crate::NodeId> {
366-
crate::EdgeTableColumn::new(self.child_slice())
379+
/// Table column with ergonomic indexing.
380+
pub fn child_column(&self) -> impl crate::TableColumn<EdgeId, NodeId> + '_ {
381+
crate::table_column::OpaqueTableColumn(self.child_slice())
367382
}
368383

369-
pub fn left_column(&self) -> crate::EdgeTableColumn<Position> {
370-
crate::EdgeTableColumn::new(self.left_slice())
384+
/// Table column with ergonomic indexing.
385+
pub fn left_column(&self) -> impl crate::TableColumn<EdgeId, Position> + '_ {
386+
crate::table_column::OpaqueTableColumn(self.left_slice())
371387
}
372388

373-
pub fn right_column(&self) -> crate::EdgeTableColumn<Position> {
374-
crate::EdgeTableColumn::new(self.right_slice())
389+
/// Table column with ergonomic indexing.
390+
pub fn right_column(&self) -> impl crate::TableColumn<EdgeId, Position> + '_ {
391+
crate::table_column::OpaqueTableColumn(self.right_slice())
375392
}
376393

377394
/// Clear all data from the table

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ pub use site_table::{SiteTable, SiteTableRow};
117117
pub use sys::flags::*;
118118
pub use sys::NodeTraversalOrder;
119119
pub use table_collection::TableCollection;
120-
pub use table_column::{EdgeTableColumn, NodeTableColumn};
120+
pub use traits::TableColumn;
121121
pub use traits::IndividualLocation;
122122
pub use traits::IndividualParents;
123123
pub use trees::{Tree, TreeSequence};

src/node_table.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -807,20 +807,20 @@ impl NodeTable {
807807
/// Get the population column as a slice
808808
=> population, population_slice_raw, crate::sys::bindings::tsk_id_t);
809809

810-
pub fn individual_column(&self) -> crate::table_column::NodeTableColumn<IndividualId> {
811-
crate::NodeTableColumn::new(self.individual_slice())
810+
pub fn individual_column(&self) -> impl crate::TableColumn<NodeId, IndividualId> + '_ {
811+
crate::table_column::OpaqueTableColumn(self.individual_slice())
812812
}
813813

814-
pub fn population_column(&self) -> crate::NodeTableColumn<PopulationId> {
815-
crate::NodeTableColumn::new(self.population_slice())
814+
pub fn population_column(&self) -> impl crate::TableColumn<NodeId, PopulationId> + '_ {
815+
crate::table_column::OpaqueTableColumn(self.population_slice())
816816
}
817817

818-
pub fn time_column(&self) -> crate::NodeTableColumn<Time> {
819-
crate::NodeTableColumn::new(self.time_slice())
818+
pub fn time_column(&self) -> impl crate::TableColumn<NodeId, Time> + '_ {
819+
crate::table_column::OpaqueTableColumn(self.time_slice())
820820
}
821821

822-
pub fn flags_column(&self) -> crate::NodeTableColumn<NodeFlags> {
823-
crate::NodeTableColumn::new(self.flags_slice())
822+
pub fn flags_column(&self) -> impl crate::TableColumn<NodeId, NodeFlags> + '_ {
823+
crate::table_column::OpaqueTableColumn(self.flags_slice())
824824
}
825825

826826
/// Clear all data from the table

src/table_column.rs

Lines changed: 14 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,19 @@
1-
macro_rules! make_table_column {
2-
($name: ident, $index: ident) => {
3-
/// Immutable view of a column
4-
#[derive(Clone, Debug)]
5-
#[repr(transparent)]
6-
pub struct $name<'table, T>(&'table [T]);
1+
#[repr(transparent)]
2+
#[derive(Clone)]
3+
pub(crate) struct OpaqueTableColumn<'table, T>(pub(crate) &'table [T]);
74

8-
impl<'table, T> $name<'table, T> {
9-
pub(crate) fn new(column: &'table [T]) -> $name<'table, T> {
10-
Self(column)
11-
}
5+
impl<T> std::ops::Index<usize> for OpaqueTableColumn<'_, T> {
6+
type Output = T;
127

13-
/// View the underlying slice
14-
pub fn as_slice(&self) -> &[T] {
15-
self.0
16-
}
17-
18-
pub fn get_with_id(&self, index: crate::$index) -> Option<&T> {
19-
self.get_with_usize(usize::try_from(index).ok()?)
20-
}
21-
22-
pub fn get_with_size_type(&self, index: crate::SizeType) -> Option<&T> {
23-
self.get_with_usize(usize::try_from(index).ok()?)
24-
}
25-
26-
pub fn get_with_usize(&self, index: usize) -> Option<&T> {
27-
self.0.get(index)
28-
}
29-
}
30-
31-
impl<T> std::ops::Index<usize> for $name<'_, T> {
32-
type Output = T;
33-
fn index(&self, index: usize) -> &Self::Output {
34-
&self.0[index]
35-
}
36-
}
37-
38-
impl<T> std::ops::Index<crate::$index> for $name<'_, T> {
39-
type Output = T;
40-
fn index(&self, index: crate::$index) -> &Self::Output {
41-
&self.0[usize::try_from(index).unwrap()]
42-
}
43-
}
8+
fn index(&self, index: usize) -> &Self::Output {
9+
&self.0[index]
10+
}
11+
}
4412

45-
impl<T> std::ops::Index<crate::SizeType> for $name<'_, T> {
46-
type Output = T;
47-
fn index(&self, index: crate::SizeType) -> &Self::Output {
48-
&self.0[usize::try_from(index).unwrap()]
49-
}
50-
}
13+
impl<T> std::ops::Index<crate::SizeType> for OpaqueTableColumn<'_, T> {
14+
type Output = T;
5115

52-
impl<T> std::convert::AsRef<[T]> for $name<'_, T> {
53-
fn as_ref(&self) -> &[T] {
54-
self.0
55-
}
56-
}
57-
};
16+
fn index(&self, index: crate::SizeType) -> &Self::Output {
17+
&self.0[usize::try_from(index).unwrap()]
18+
}
5819
}
59-
60-
make_table_column!(NodeTableColumn, NodeId);
61-
make_table_column!(EdgeTableColumn, EdgeId);

src/traits.rs

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,3 +119,111 @@ impl_individual_parents!(
119119
);
120120
impl_individual_parents!(N, usize, &[crate::IndividualId; N], self, self.as_slice());
121121
impl_individual_parents!(N, usize, [crate::IndividualId; N], self, self.as_slice());
122+
123+
mod private {
124+
pub trait NewTypeMarker: TryInto<usize, Error = crate::TskitError> {}
125+
pub trait TableColumnMarker {}
126+
}
127+
128+
impl private::NewTypeMarker for crate::EdgeId {}
129+
impl private::NewTypeMarker for crate::NodeId {}
130+
impl private::NewTypeMarker for crate::SiteId {}
131+
impl private::NewTypeMarker for crate::MutationId {}
132+
impl private::NewTypeMarker for crate::MigrationId {}
133+
impl private::NewTypeMarker for crate::IndividualId {}
134+
impl private::NewTypeMarker for crate::PopulationId {}
135+
#[cfg(feature = "provenance")]
136+
#[cfg_attr(doc_cfg, doc(cfg(feature = "provenance")))]
137+
impl private::NewTypeMarker for crate::ProvenanceId {}
138+
139+
/// Interface of a non-ragged table column.
140+
///
141+
/// Unlike slice views of table columns, this API
142+
/// allows indexed via row id types and [`crate::SizeType`].
143+
///
144+
/// # Notes
145+
///
146+
/// * This trait is sealed.
147+
///
148+
/// # For C programmers
149+
///
150+
/// The `C` programming language allows implicit casts between
151+
/// integer types.
152+
/// This implicit behavior allows one to index a table column
153+
/// using a row id type ([`crate::bindings::tsk_id_t`]) because
154+
/// the compiler will cast it to `size_t`.
155+
///
156+
/// `rust` does not allow implicit casts, which makes working
157+
/// with table columns as slices awkward.
158+
/// One has to manually cast the id type and the resulting code isn't
159+
/// nice to read.
160+
///
161+
/// This trait solves that problem by requiring that [`std::ops::Index`]
162+
/// by implemented for types that one would like to use as indexes
163+
/// in the `tskit` world.
164+
pub trait TableColumn<I, T>:
165+
std::ops::Index<I, Output = T>
166+
+ std::ops::Index<usize, Output = T>
167+
+ std::ops::Index<crate::SizeType, Output = T>
168+
+ private::TableColumnMarker
169+
{
170+
/// Get the underlying slice
171+
fn as_slice(&self) -> &[T];
172+
173+
/// Get with a table row identifier such as [`crate::NodeId`]
174+
fn get_with_id(&self, at: I) -> Option<&T>;
175+
176+
/// The "standard" get function
177+
fn get(&self, at: usize) -> Option<&T> {
178+
self.as_slice().get(at)
179+
}
180+
181+
/// Get with [`crate::SizeType`]
182+
fn get_with_size_type(&self, at: crate::SizeType) -> Option<&T> {
183+
self.as_slice().get(usize::try_from(at).ok()?)
184+
}
185+
186+
/// Iterator over the data.
187+
fn iter<'a, 'b>(&'a self) -> impl Iterator<Item = &'b T>
188+
where
189+
'a: 'b,
190+
T: 'b,
191+
{
192+
self.as_slice().iter()
193+
}
194+
195+
/// Column length
196+
fn len(&self) -> usize {
197+
self.as_slice().len()
198+
}
199+
200+
/// Query if column is empty
201+
fn is_empty(&self) -> bool {
202+
self.as_slice().is_empty()
203+
}
204+
}
205+
206+
impl<T> private::TableColumnMarker for crate::table_column::OpaqueTableColumn<'_, T> {}
207+
208+
impl<I, T> std::ops::Index<I> for crate::table_column::OpaqueTableColumn<'_, T>
209+
where
210+
I: private::NewTypeMarker,
211+
{
212+
type Output = T;
213+
fn index(&self, index: I) -> &Self::Output {
214+
&self.0[index.try_into().unwrap()]
215+
}
216+
}
217+
218+
impl<I, T> TableColumn<I, T> for crate::table_column::OpaqueTableColumn<'_, T>
219+
where
220+
I: private::NewTypeMarker,
221+
{
222+
fn as_slice(&self) -> &[T] {
223+
self.0
224+
}
225+
226+
fn get_with_id(&self, at: I) -> Option<&T> {
227+
self.0.get(at.try_into().ok()?)
228+
}
229+
}

tests/test_tables.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use tskit::TableColumn;
2+
13
#[test]
24
fn test_empty_table_collection() {
35
macro_rules! validate_empty_tables {

0 commit comments

Comments
 (0)