I created a smaller example here:
#![feature(generic_associated_types)]
mod deref_owned {
use std::borrow::Cow;
use std::ops::{Deref, DerefMut};
/// Smart pointer to owned inner value
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct Owned<T>(pub T);
impl<T> Deref for Owned<T> {
type Target = T;
/// Returns a reference to the contained value
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T> DerefMut for Owned<T> {
/// Returns a mutable reference to the contained value
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
/// Pointer types that can be converted into an owned type
pub trait IntoOwned: Sized {
/// The type the pointer can be converted into
type Owned;
/// Convert into owned type
fn into_owned(self) -> Self::Owned;
}
impl<'a, T> IntoOwned for &'a T
where
T: ?Sized + ToOwned,
{
type Owned = <T as ToOwned>::Owned;
fn into_owned(self) -> Self::Owned {
self.to_owned()
}
}
impl<'a, T> IntoOwned for Cow<'a, T>
where
T: ?Sized + ToOwned,
{
type Owned = <T as ToOwned>::Owned;
fn into_owned(self) -> <Self as IntoOwned>::Owned {
Cow::into_owned(self)
}
}
impl<T> IntoOwned for Owned<T> {
type Owned = T;
fn into_owned(self) -> Self::Owned {
self.0
}
}
}
use deref_owned::*;
use std::fmt::Debug;
use std::ops::Deref;
trait Parse<T: ?Sized> {
type Pointer<'a>: Deref<Target = T> + IntoOwned + Debug
where
Self: 'a;
fn parse(&self) -> Option<Self::Pointer<'_>>;
}
impl Parse<[u8]> for [u8] {
type Pointer<'a> = &'a [u8]; // zero-cost
fn parse(&self) -> Option<Self::Pointer<'_>> {
Some(self)
}
}
impl Parse<str> for [u8] {
type Pointer<'a> = &'a str; // zero-cost
fn parse(&self) -> Option<Self::Pointer<'_>> {
std::str::from_utf8(self).ok()
}
}
impl Parse<i32> for [u8] {
type Pointer<'a> = Owned<i32>; // smart pointer that drops owned pointee
/*
type Pointer<'a> = i32; // won't work
type Pointer<'a> = std::borrow::Cow<'static, i32>; // comes with runtime overhead
type Pointer<'a> = Box<i32>; // unnecessary heap allocation
*/
fn parse(&self) -> Option<Self::Pointer<'_>> {
Parse::<str>::parse(self)
.and_then(|s| s.parse::<i32>().ok())
.map(|i| Owned(i))
//.map(|i| std::borrow::Cow::Owned(i)) // runtime overhead
//.map(|i| Box::new(i)) // heap allocation
}
}
fn foo<T, U>(input: &U)
where
T: ?Sized,
U: ?Sized + Parse<T>,
{
println!(
"Parsed as {}: {:?}",
std::any::type_name::<T>(),
input.parse()
);
}
fn bar(input: &(impl ?Sized + Parse<str>)) {
match input.parse() {
Some(parsed) => println!(
"{} has {} bytes and {} chars",
&*parsed,
parsed.len(),
parsed.chars().count()
),
None => println!("could not parse"),
}
}
fn main() {
let s = b"12345" as &[u8];
foo::<[u8], _>(s);
foo::<str, _>(s);
foo::<i32, _>(s);
bar(b"H\xC3\xBChnerleiter" as &[u8]);
}
Output:
Parsed as [u8]: Some([49, 50, 51, 52, 53])
Parsed as str: Some("12345")
Parsed as i32: Some(Owned(12345))
Hühnerleiter has 13 bytes and 12 chars
Note how I could use Cow
or Box
(if I remove the IntoOwned
bound, since it's not implemented yet for Box<T> where T: Sized
), but each comes with overhead.
Now this is interesting. Look at Box::into_inner
, which is unstable. See also tracking issue #80437. It might be another use case for an IntoOwned
(or "consuming" ToOwned
) trait.
I can't replace Owned<i32>
with just i32
in the above example of impl Parse<i32> for [u8]
. At least I didn't find a way how to do it.
Beside being Deref
, it manages the ownership of the pointee (i.e. the pointer will drop the pointee when the pointer is dropped). That sort of management is one of the aspects a smart pointer is about, I guess.