Skip to content

Instantly share code, notes, and snippets.

@pchampin
Created December 18, 2019 18:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save pchampin/7fbf3262ab22b6a0f6a6eae8a6aa938c to your computer and use it in GitHub Desktop.
Save pchampin/7fbf3262ab22b6a0f6a6eae8a6aa938c to your computer and use it in GitHub Desktop.
//! 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;
// **** Dummy Triple trait
pub trait Triple {
type Term: AsRef<str> + ?Sized;
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 = str;
fn s(&self) -> &Self::Term {
&self
}
// TODO implement p() and o()
}
// UnsafeTriple trait
/// This trait should not be used directly.
/// It is part of the mechanics that allow `GIterstor`s to work.
pub trait UnsafeTriple {
type Term: AsRef<str> + ?Sized;
#[inline]
unsafe fn us(&self) -> &Self::Term;
#[inline]
unsafe fn up(&self) -> &Self::Term {
self.us() // just for faster prototyping
}
#[inline]
unsafe fn uo(&self) -> &Self::Term {
self.us() // just for faster prototyping
}
}
impl<T: Triple> UnsafeTriple for T {
type Term = T::Term;
unsafe fn us(&self) -> &Self::Term {
self.s()
}
// TODO implement p() and o()
}
impl<T: Triple> UnsafeTriple for *const T {
type Term = T::Term;
unsafe fn us(&self) -> &Self::Term {
(**self).s()
}
// TODO implement p() and o()
}
impl<T: AsRef<str> + ?Sized> UnsafeTriple for [*const T;3] {
type Term = T;
unsafe fn us(&self) -> &Self::Term {
&*self[0]
}
}
/// Safe abstraction used by `Graphs`'s iterators.
///
/// See
/// * Graph.guard_triple
/// * Graph.guard_triple_ref
/// * Graph.guard_term_refs
///
#[derive(Debug)]
pub struct GuardedTriple<'a, T: UnsafeTriple> {
_phantom: PhantomData<&'a T>,
triple: T,
}
impl<'a, T: UnsafeTriple> Triple for GuardedTriple<'a, T> {
type Term = T::Term;
fn s(&self) -> &Self::Term {
unsafe { self.triple.us() }
}
// 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=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: UnsafeTriple;
fn triples<'s>(&'s self) -> GIterator<'s, Self>;
/// To be used in iterators, when Self::Triple is T
fn guard_triple<'s>(&'s self, triple: Self::Triple) -> GuardedTriple<'s, Self::Triple>where
Self::Triple: Triple,
{
GuardedTriple {
_phantom: PhantomData,
triple,
}
}
/// To be used in iterators, when Self::Triple is *const T
fn guard_triple_ref<'s, T>(&'s self, triple: &'s T) -> GuardedTriple<'s, Self::Triple>where
T: Triple,
Self: Graph<Triple = *const T>,
{
GuardedTriple {
_phantom: PhantomData,
triple,
}
}
/// To be used in iterators, when Self::Triple is [*const T; 3]
fn guard_term_refs<'s, T>(&'s self, s: &'s T, p: &'s T, o: &'s T) -> GuardedTriple<'s, Self::Triple> where
T: AsRef<str> + ?Sized,
Self: Graph<Triple = [*const T; 3]>,
{
GuardedTriple {
_phantom: PhantomData,
triple: [s, p, o],
}
}
}
// 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| self.guard_triple(t.clone())))
}
}
struct Test2<T: Triple> (Vec<T>);
impl<T: Triple + Clone> Graph for Test2<T> {
type Triple = *const T;
fn triples<'s>(&'s self) -> GIterator<'s, Self> {
Box::new(self.0.iter().map(move |t| self.guard_triple_ref(t)))
}
}
struct Test3<T: Triple> (Vec<T>);
impl<T> Graph for Test3<T> where
T: Triple + Clone,
{
type Triple = [*const T::Term;3];
fn triples<'s>(&'s self) -> GIterator<'s, Self> {
Box::new(self.0.iter().map(move |t| self.guard_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