-
-
Save MattesWhite/f5daf2d38d36c4e325e00cbb41ec59e8 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//! This is an experiment on how to get rid of lifetime parameters in | |
//! Sopghia's Graph and Dataset traits. It demonstrates the general idea | |
//! on a simplified version of Triple and Graph. | |
//! | |
//! Graph iterators no longer return Graph::Triple's, | |
//! they return a safe abstraction around it: GuardedTRiple<'a, Graph::Triple>. | |
//! | |
//! Graph::Triple itself can be one of several options: | |
//! * T (where T implements Triple) | |
//! * *const T (where T implements Triple) | |
//! * [*const T; 3] (where T is Term<X>) | |
//! | |
//! Intuitively: | |
//! * the first option must be used when triples are created during iteration, | |
//! and their ownership is transferred to the caller; | |
//! * the second option must be used when triples are owned by the Graph, | |
//! and the caller borrows them for the lifetime of the iterator; | |
//! * the third option must be used when the Graph owns its terms, | |
//! but does not own triples per se. | |
//! | |
//! The triples() method (and all its sister methods) | |
//! must use one of the methods provided by the Graph interface | |
//! to produce GuardedTriple: | |
//! * guard_triple for the first option | |
//! * guard_triple_ref for the second option | |
//! * guard_term_refs for the second option | |
//! These methods guarantee that the inner pointers will never be used | |
//! unsafely. | |
//! | |
//! NB: technically, the three option above are the three provided implementation | |
//! of the UnsafeTriple trait, which should not be used directly. | |
//! In the future, more options could be provided by adding more implementations, | |
//! but users of the Sophia crate are not expected to do that. | |
// **** Dummy Triple trait | |
pub trait Triple { | |
type Term: AsRef<str>; | |
fn s(&self) -> &Self::Term; | |
fn p(&self) -> &Self::Term { | |
self.s() // just for faster prototyping | |
} | |
fn o(&self) -> &Self::Term { | |
self.s() // just for faster prototyping | |
} | |
} | |
/// **** Dummy Triple implementation | |
impl Triple for String { | |
type Term = String; | |
fn s(&self) -> &Self::Term { | |
&self | |
} | |
// TODO implement p() and o() | |
} | |
/// **** Dummy Triple implementation | |
impl<T: AsRef<str>> Triple for [T; 3] { | |
type Term = T; | |
fn s(&self) -> &Self::Term { | |
&self[0] | |
} | |
// TODO implement p() and o() | |
} | |
/// Wrapper around triples of a `Graph`. | |
/// | |
/// This type's main purpose is to avoid a lifetime-parameter on the | |
/// `Graph` trait. | |
/// | |
#[derive(Debug)] | |
pub enum GuardedTriple<'a, T: Triple> { | |
OwnedTriple(T), | |
BorrowedTriple(&'a T), | |
BorrowedTerms { | |
s: &'a T::Term, | |
p: &'a T::Term, | |
o: &'a T::Term, | |
}, | |
} | |
impl<'a, T: Triple> Triple for GuardedTriple<'a, T> { | |
type Term = T::Term; | |
fn s(&self) -> &Self::Term { | |
use self::GuardedTriple::*; | |
match self { | |
OwnedTriple(t) => t.s(), | |
BorrowedTriple(t) => t.s(), | |
BorrowedTerms { s, ..} => s, | |
} | |
} | |
// TODO implement p() and o() | |
} | |
impl<'src, T: Triple> GuardedTriple<'src, T> { | |
/// Construct a `GuardedTriple` by **taking** a `Triple`. | |
pub fn from_triple(triple: T) -> Self { | |
Self::OwnedTriple(triple) | |
} | |
/// Construct a `GuardedTriple` by **borrowing** a `Triple`. | |
pub fn from_triple_ref(triple: &'src T) -> Self { | |
Self::BorrowedTriple(triple) | |
} | |
} | |
impl<'src, T: AsRef<str>> GuardedTriple<'src, [T; 3]> { | |
/// Construct a `GuardedTriple` from **borrowed `Term`s**. | |
pub fn from_spo_refs(s: &'src T, p: &'src T, o: &'src T) -> Self { | |
Self::BorrowedTerms { | |
s, p, o, | |
} | |
} | |
} | |
// Dummy Graph trait, with the minimally required structure | |
/// Type alias for all iterators returned by `Graph` methods. | |
type GIterator<'a, G> = Box<dyn Iterator<Item=GuardedTriple<'a, <G as Graph>::Triple>> + 'a>; | |
pub trait Graph { | |
/// This type is the one used *indternally* by `GIterator`s. | |
/// What users will get is a safe wrapper around this type. | |
/// This should be one of the 3 options documented at the top of this file. | |
type Triple: Triple; | |
fn triples<'s>(&'s self) -> GIterator<'s, Self>; | |
} | |
// Testing the different ways to implement Graph iterators | |
struct Test1<T: Triple> (Vec<T>); | |
impl<T: Triple + Clone> Graph for Test1<T> { | |
type Triple = T; | |
fn triples<'s>(&'s self) -> GIterator<'s, Self> { | |
Box::new(self.0.iter().map(move |t| GuardedTriple::from_triple(t.clone()))) | |
} | |
} | |
struct Test2<T: Triple> (Vec<T>); | |
impl<T: Triple + Clone> Graph for Test2<T> { | |
type Triple = T; | |
fn triples<'s>(&'s self) -> GIterator<'s, Self> { | |
Box::new(self.0.iter().map(move |t| GuardedTriple::from_triple_ref(t))) | |
} | |
} | |
struct Test3<T: Triple> (Vec<T>); | |
impl<T> Graph for Test3<T> where | |
T: Triple + Clone, | |
T::Term: Sized, | |
{ | |
type Triple = [T::Term;3]; | |
fn triples<'s>(&'s self) -> GIterator<'s, Self> { | |
Box::new(self.0.iter().map(move |t| GuardedTriple::from_spo_refs(t.s(), t.p(), t.o()))) | |
} | |
} | |
fn main() { | |
let v = vec!["foo".to_string(), "bar".to_string()]; | |
println!("\nTest1"); | |
for t in Test1(v.clone()).triples() { | |
dbg!(&t); | |
println!("v: {}", t.s()); | |
} | |
println!("\nTest2"); | |
for t in Test2(v.clone()).triples() { | |
dbg!(&t); | |
println!("v: {}", t.s()); | |
} | |
println!("\nTest3"); | |
for t in Test3(v.clone()).triples() { | |
dbg!(&t); | |
println!("v: {}", t.s()); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//! This is an experiment on how to get rid of lifetime parameters in | |
//! Sopghia's Graph and Dataset traits. It demonstrates the general idea | |
//! on a simplified version of Triple and Graph. | |
//! | |
//! Graph iterators no longer return Graph::Triple's, | |
//! they return a safe abstraction around it: GuardedTRiple<'a, Graph::Triple>. | |
//! | |
//! Graph::Triple itself can be one of several options: | |
//! * T (where T implements Triple) | |
//! * *const T (where T implements Triple) | |
//! * [*const T; 3] (where T is Term<X>) | |
//! | |
//! Intuitively: | |
//! * the first option must be used when triples are created during iteration, | |
//! and their ownership is transferred to the caller; | |
//! * the second option must be used when triples are owned by the Graph, | |
//! and the caller borrows them for the lifetime of the iterator; | |
//! * the third option must be used when the Graph owns its terms, | |
//! but does not own triples per se. | |
//! | |
//! The triples() method (and all its sister methods) | |
//! must use one of the methods provided by the Graph interface | |
//! to produce GuardedTriple: | |
//! * guard_triple for the first option | |
//! * guard_triple_ref for the second option | |
//! * guard_term_refs for the second option | |
//! These methods guarantee that the inner pointers will never be used | |
//! unsafely. | |
//! | |
//! NB: technically, the three option above are the three provided implementation | |
//! of the UnsafeTriple trait, which should not be used directly. | |
//! In the future, more options could be provided by adding more implementations, | |
//! but users of the Sophia crate are not expected to do that. | |
use std::marker::PhantomData; | |
use std::fmt::Debug; | |
use std::ptr::NonNull; | |
// **** Dummy Triple trait | |
pub trait Triple: Debug { | |
type Term: AsRef<str> + Debug; | |
fn s(&self) -> &Self::Term; | |
fn p(&self) -> &Self::Term { | |
self.s() // just for faster prototyping | |
} | |
fn o(&self) -> &Self::Term { | |
self.s() // just for faster prototyping | |
} | |
} | |
/// **** Dummy Triple implementation | |
impl Triple for String { | |
type Term = String; | |
fn s(&self) -> &Self::Term { | |
&self | |
} | |
// TODO implement p() and o() | |
} | |
/// **** Dummy Triple implementation | |
impl<Term> Triple for [Term; 3] | |
where | |
Term: AsRef<str> + Debug, | |
{ | |
type Term = Term; | |
fn s(&self) -> &Self::Term { | |
&self[0] | |
} | |
// TODO implement p() and o() | |
} | |
/// Marker trait that states the origin of a triple handed out. | |
/// | |
/// All implementors are type-level-constucts. As such they can not be | |
/// instanciated by the user. Everything nesessary is handled in a zero-cost | |
/// way internally. | |
pub trait TripleOrigin: Triple {} | |
/// States that handed out triples are created on-demand. | |
#[derive(Debug)] | |
struct CreatedTriple<T: Triple>(T); | |
impl<T: Triple> Triple for CreatedTriple<T> { | |
type Term = T::Term; | |
fn s(&self) -> &Self::Term { | |
self.0.s() | |
} | |
} | |
impl<T: Triple> TripleOrigin for CreatedTriple<T> {} | |
/// States that handed out triples are stored inside and handed out by | |
/// reference. | |
#[derive(Debug)] | |
struct StoredTriple<T: Triple>(NonNull<T>); | |
impl<T: Triple> Triple for StoredTriple<T> { | |
type Term = T::Term; | |
fn s(&self) -> &Self::Term { | |
unsafe { self.0.as_ref().s() } | |
} | |
} | |
impl<T: Triple> TripleOrigin for StoredTriple<T> {} | |
/// States that handed out triples constructed from references to terms that | |
/// are stored inside. | |
#[derive(Debug)] | |
struct StoredTerms<Term: AsRef<str> + Debug> { | |
s: NonNull<Term>, | |
p: NonNull<Term>, | |
o: NonNull<Term>, | |
} | |
impl<Term: AsRef<str> + Debug> Triple for StoredTerms<Term> { | |
type Term = Term; | |
fn s(&self) -> &Self::Term { | |
unsafe { self.s.as_ref() } | |
} | |
} | |
impl<Term: AsRef<str> + Debug> TripleOrigin for StoredTerms<Term> {} | |
/// Triples handed out by a `Graph`. | |
/// | |
/// The lifetime of `GraphTriple` ensures that the `Graph` is recognized as | |
/// borrowed immutable by the compiler ensuring that the `Graph` is not mutated | |
/// while handing out triples. | |
/// | |
/// # Overhead | |
/// | |
/// This type incures zero memory-overhead and is just a type-level work around | |
/// until GATs arrive. | |
#[derive(Debug)] | |
pub struct GraphTriple<'a, T: TripleOrigin> { | |
_phantom: PhantomData<&'a T>, | |
triple: T, | |
} | |
impl<'a, T> GraphTriple<'a, CreatedTriple<T>> | |
where | |
T: Triple | |
{ | |
/// Wraps a newly created triple. | |
/// | |
/// The lifetime is defined by the calling context. | |
fn from_triple(triple: T) -> Self { | |
Self { | |
_phantom: PhantomData, | |
triple: CreatedTriple(triple), | |
} | |
} | |
} | |
impl<'a, T> GraphTriple<'a, StoredTriple<T>> | |
where | |
T: Triple | |
{ | |
/// Wraps a referenced triple. | |
fn from_triple_ref(triple: &'a T) -> Self { | |
Self { | |
_phantom: PhantomData, | |
triple: StoredTriple(NonNull::from(triple)), | |
} | |
} | |
} | |
impl<'a, Term> GraphTriple<'a, StoredTerms<Term>> | |
where | |
Term: AsRef<str> + Debug, | |
{ | |
/// Creates a new triple from references to terms. | |
fn from_term_refs(s: &'a Term, p: &'a Term, o: &'a Term) -> Self { | |
Self { | |
_phantom: PhantomData, | |
triple: StoredTerms { | |
s: NonNull::from(s), | |
p: NonNull::from(p), | |
o: NonNull::from(o), | |
}, | |
} | |
} | |
} | |
impl<'a, T: TripleOrigin> Triple for GraphTriple<'a, T> { | |
type Term = T::Term; | |
fn s(&self) -> &Self::Term { | |
self.triple.s() | |
} | |
// TODO implement p() and o() | |
} | |
// Dummy Graph trait, with the minimally required structure | |
/// Type alias for all iterators returned by `Graph` methods. | |
type GIterator<'a, G> = Box<dyn Iterator<Item=GraphTriple<'a, <G as Graph>::IterContent>> + 'a>; | |
pub trait Graph { | |
/// Tells the compiler where the triples handed out by the `Graph`'s | |
/// methods originate from. | |
/// | |
/// This also defines how the handed-out `GraphTriples` are build: | |
/// * `CreatedTriple` use `GraphTriple::from_triple()` | |
/// * `StoredTriple` use `GraphTriple::from_triple_ref()` | |
/// * `StoredTerms` use `GraphTriple::from_term_refs()` | |
/// | |
/// _Note:_ Both `TripleOrigin` and `GraphTriple` are zero-cost | |
/// abstractions. Consequently, this type-level-construct will not harm | |
/// performance. | |
type IterContent: TripleOrigin; | |
fn triples<'s>(&'s self) -> GIterator<'s, Self>; | |
} | |
// Testing the different ways to implement Graph iterators | |
struct Test1<T: Triple> (Vec<T>); | |
impl<T: Triple + Clone> Graph for Test1<T> { | |
type IterContent = CreatedTriple<T>; | |
fn triples<'s>(&'s self) -> GIterator<'s, Self> { | |
Box::new(self.0.iter().map(move |t| GraphTriple::from_triple(t.clone()))) | |
} | |
} | |
struct Test2<T: Triple> (Vec<T>); | |
impl<T: Triple + Clone> Graph for Test2<T> { | |
type IterContent = StoredTriple<T>; | |
fn triples<'s>(&'s self) -> GIterator<'s, Self> { | |
Box::new(self.0.iter().map(move |t| GraphTriple::from_triple_ref(t))) | |
} | |
} | |
struct Test3<T: Triple> (Vec<T>); | |
impl<T> Graph for Test3<T> where | |
T: Triple + Clone, | |
{ | |
type IterContent = StoredTerms<T::Term>; | |
fn triples<'s>(&'s self) -> GIterator<'s, Self> { | |
Box::new(self.0.iter().map(move |t| GraphTriple::from_term_refs(t.s(), t.p(), t.o()))) | |
} | |
} | |
fn main() { | |
let v = vec!["foo".to_string(), "bar".to_string()]; | |
println!("\nTest1"); | |
for t in Test1(v.clone()).triples() { | |
dbg!(&t); | |
println!("v: {}", t.s()); | |
} | |
println!("\nTest2"); | |
for t in Test2(v.clone()).triples() { | |
dbg!(&t); | |
println!("v: {}", t.s()); | |
} | |
println!("\nTest3"); | |
for t in Test3(v.clone()).triples() { | |
dbg!(&t); | |
println!("v: {}", t.s()); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment