Of course, it was totally useless then.
This whole issue seems super complex. It took me a lot of time to understand it, and I remember fragments of what I learned throughout this discussion again (like how deref is transitive, etc.) which help me to get a better understanding. But I still feel like I don't have a good overview on the issue yet.
I didn't have time to try it yet, but I have looked at it and it inspired me to get rid of the Owned
wrapper. My code compiled, but it wasn't useful (because IntoOwned<String>
doesn't help me where I need IntoOwned<str>
, for example). I suspect your code has the same issue.
Maybe you fix it with all these implementations of IntoOwnedBorrow<Pointee> for Pointer
(IOB<[T]> for Vec<T>
, IOB<str> for String
, etc). This is what I had way earlier in version 0.3.0 of deref_owned
, see here, but I don't like that much because newly added smart pointers would not work unless I provide an implementation in a private module. (Perhaps I misunderstand it though.)
Also auto traits or negative impls didn't help (because they don't affect overlapping rules if I understand right).
In the end, I gave up to get rid of the wrapper.
However, I was able to simply deref_owned
drastically in version 0.8.0 (see also changelog and documentation):
- Struct
Owned
is now a simple wrapper with one type argument (of its inner value). - The associated type
GenericCow<B>::Owned
has been removed in favor of using<B as ToOwned>::Owned
.
Moreover, I removed the terminology "smart pointer" as you and @8573 suggested.
Here is an example test case:
#[test]
fn test_generic_fn() {
fn generic_fn(arg: impl GenericCow<str>) {
let reference: &str = arg.borrow();
assert_eq!(reference, "Echo");
let owned: String = arg.into_owned();
assert_eq!(owned, "Echo".to_string());
}
generic_fn(Owned("Echo".to_string()));
generic_fn(Cow::Owned("Echo".to_string()));
generic_fn(Cow::Borrowed("Echo"));
generic_fn("Echo");
}
(Playground) (source on docs.rs)
mmtkvdb
now uses this new version and it seems fine. Nonetheless, the API requires some cleanup, I think. But at least it works for now.
P.S.: deref_owned
is now mostly doing the same as what "conradludgate" showed on IRLO.
What I mean is this:
// implementation details that are only pub because of the private_in_public lint
mod details {
/* … */
impl<T: Clone> IntoOwnedBorrow<[T]> for Vec<T> {
fn into_owned(this: Self) -> Vec<T> {
this
}
}
impl IntoOwnedBorrow<str> for String {
fn into_owned(this: Self) -> String {
this
}
}
/* … */
What about Path
and PathBuf
or third-party type-pairs like that? They won't be supported unless implementations for them are added into the module details
, which is private though .
I'm not sure if I fully understand your comment though.
You write:
I don't think it's only a formality. It would be if Storable
was a sealed trait. But third-party crates may implement it too (and it's intended to be implemented by external crates).
Actually Owned<T>
is already an implementation detail. Users of mmtkvdb
's API don't need to know about it. They can rely on the fact that the aligned reference-like type implements GenericCow<Self>
:
pub unsafe trait Storable: Ord + ToOwned {
/* … */
type AlignedRef<'a>: GenericCow<Self>;
/* … */
}
This bound using GenericCow
is sufficient to get a borrow of the storable type or to retrieve an owned form of it (edit: see also fn generic_fn()
in test case above). No knowledge about Owned
is needed.
Edit: I added some links now that docs.rs processed the package.
I guess most people still frown upon the idea of a wrapper:
pub struct Owned<T>(pub T);
If there are more positive or negative opinions, I'm happy to hear them. I would like to understand better if what I was doing is plain nonsense or is reasonable to do. So far no other solution has been presented that I find reasonable (see my comments above regarding your proposal @CAD97). Maybe the Owned
wrapper can be made superfluous in a future version of Rust or with specialization or similar features, but I think that's all very very unstable yet, right?
I guess the central problem is that we have no clear definition of what's "owned" in Rust:
String
is an ownedString
i32
is an ownedi32
&String
is an owned reference to aString
&str
is an owned reference to a string sliceRc<T>
is an ownedRc
pointing to aT
So while that all may be true, it is not helping us. In that sense: EVERYTHING IS OWNED.
Consequently, if we have a trait say OwnedType
, and we implement
impl<T> OwnedType for T { /* … */ }
then this would be in conflict with any other implementation.
I tried to do things like:
impl<B> OwnedType<B> for <B as ToOwned>::Owned { /* … */ }
to restrict the definition from "any owned type" to "an owned type for a particular borrowed type B
", but apparently that doesn't satisfy the compiler either because it potentially collides. (See also this post on IRLO.)
We can help us by using a wrapper Owned<T>
to explicitly mark a type as "owned". (Here T
is the type that's passed as "owned".)
This doesn't look much more different than borrowing or making a Cow
:
Owned(x)
Cow::Owned(x)
Cow::Borrowed(&x)
&x
Say x
is our String
, then in each of these four variants, we wrap this String
in some way.
- as owned (determined at compile-time)
- as dynamically chosen to be owned
- as dynamically chosen to be borrowed
- as borrowed (determined at compile-time)
Thus the transformation x
→ Owned(x)
isn't much different than x
→ &x
, or x
→ Cow::Owned(x)
.
But perhaps someone with better background on type theory can explain this better. This is merely how I feel about it without having too deep understanding about the mathematical background theory.
Is this a good approach or a bad one? And if it's a bad one, what to do else? A simple use case to discuss this could be the following function:
fn generic_fn(arg: impl GenericCow<str>) {
let reference: &str = arg.borrow();
assert_eq!(reference, "Echo");
let owned: String = arg.into_owned();
assert_eq!(owned, "Echo".to_string());
}
(from the Playground above)
This function might sometimes (depending on run-time choices) require a borrow and sometimes an owned value. When it requires an owned value and the caller has an owned value that it doesn't need anymore, this can save an unnecessary clone. Using Cow
here has runtime-overhead because it always dynamically determines if a value is borrowed or owned. In contrast GenericCow
can determine it at compile-time (when possible).
P.S.: I feel like Cow
is such a "basic" data type, that the proposal to add something like GenericCow
to std
is not entirely crazy. There was the argument that Cow
doesn't have much overhead, but I responded on IRLO:
Yet we have no GenericCow
yet (in std
).