I have a "view" data structure in Rust that contains a reference to another data structure, and I want to automate testing of the getter methods that I implemented on the view data structure. Here's a MRE with a single getter:
/// View data structure.
struct View<'a> {
value: &'a u32,
}
impl<'a> View<'a> {
/// Getter method.
pub fn value(&self) -> u32 {
*self.value
}
}
/// Attempt to automate unit testing of the getter: create an underlying value,
/// then create a `View`, then apply the getter method to the `View` and ensure
/// we get the expected value out.
fn check_getter(getter: fn(&View) -> u32) {
let value: u32 = 7;
let view = View { value: &value };
assert!(getter(&view) == 7);
}
check_getter(View::value);
This fails to compile, with the following error:
mismatched types
expected fn pointer `for<'r, 's> fn(&'r tests::test_adcs::View<'s>) -> _`
found fn pointer `for<'r> fn(&'r tests::test_adcs::View<'_>) -> _`
I think I need to use HRTBs, and have tried various permutations, but haven't been able to get anything to compile.
TL;DR: Use a closure (you can use macros if you prefer):
check_getter(|v| v.value());
You fell into the trap of early-bound vs. late-bound lifetimes.
View::value does not have the signature for<'a> fn <View<'a>>::value(). Rather, it has the signature fn <View<'some_concrete_lifetime>>::value(). That is, 'a is not any lifetime, but some inferred lifetime. Therefore HRTB doesn't work - only one lifetime matches the given function. You can read more about early-bound and late-bound lifetimes in this recent answer I wrote, but the crux of the issue is that because the lifetime 'a appears on the impl, not the function itself, it becomes early-bound and has only one concrete lifetime for each instance of the function instead of being late-bound and allowed to use with any lifetime.
The fix is just to wrap it with a closure, making it late-bound.
Related
I have a trait which has a function that should return an iterator of a different trait. I also need a common function which takes this trait and does operations on it.
This is the code I have currenty:
pub trait NodeLike : Sized {
/// Get the node id
fn get_id(&self) -> i32;
}
pub trait EdgeLike {
/// Get the destination node id
fn get_node_id(&self) -> i32;
/// Get the edge id
fn get_edge_id(&self) -> i32;
/// iterates of the source nodes
fn iter_source_nodes(&self) -> dyn Iterator<Item = dyn NodeLike>;
^^^^^^^^^^^^^
}
fn operation(id: i32, edge: dyn EdgeLike) {
for node in edge.iter_source_nodes().find(|n| n.get_id() == id) {
}
}
The underlined part above throws the compiler error:
the trait `NodeLike` cannot be made into an object
`NodeLike` cannot be made into an object rustc E0038
main.rs(1, 22): for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety>
Reading through the documentation, I believe my NodeLike trait is object-safe. The only function it has is &Self (i.e. &self)
Now, I can further my code by removing the Sized trait on NodeLike. However, then the error occurs in the function operation; I cannot use the find function on the iterator.
How can I change my code so that I can make NodeLike object safe, AND be able to return an iterator which I can call find on?
Following Stargateur's suggestion and your playground link, I got it to compile like this:
Playground link
This is if you do not want to use any unstable features.
The trick is to specify the explicit type of the iterator. If you don't know that type, just put any stupid type in and let the compiler complain about the type mismatch. It'll tell you that we're dealing here with a slice iter, so hence the std::slice::Iter.
The other modifications are then about adding a lifetime parameter to your struct, because iter() returns references. If you don't want that, change it to into_iter, but then you must use self instead of &self.
Also, for your operation, you must do some lifetime magic...
fn operation<'a, Item: NodeLike>(id: i32, edge: &'a impl EdgeLike<'a, Item=Item> )
If you are okay with unstable features, you can avoid having to specify the exact type of the iter: In your playground version just change the dyn to impl. The compiler will tell you that that's an unstable feature and will show you the macro to use.
Finally, to avoid having to add lifetime annotations to the trait itself, there's something about generic associated types, but maybe that's going too far for now.
I'm writing a set of benchmarks for different key-value stores, and would like to have a single adapter trait that I can use in all the benchmarks, and then implement it for each key-value store.
This worked well for two of them. However, the third required me to add a lifetime on my trait, and after fighting the borrow checker for an entire day, I still can't seem to get it right.
I've distilled it down to this minimal repro: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=54fec74cb70c63c03f25ec7a9dfc7e60
What I don't understand is why the borrow on txn lives longer than the scope of benchmark(). It seems to me that it should live for only that one line.
How can I define the AdapterTransaction trait to resolve this, that still allows implementations to choose their own return type?
edit
added that I need to be able to use the AdapterTransaction implementations with a factory trait
The main problem in your first playground is the lifetime on &self being the same as the generic lifetime on the trait.
pub trait AdapterTransaction<'a, T: AsRef<[u8]>> {
fn get(&'a self, key: &[u8]) -> Option<T>;
}
Because they are the same, it requires the borrow of the underlying type to live at least as long as the type itself. This isn't true because even though the type is owned, the borrow would only last for the duration of the function call. In benchmark<'a,...>(), the lifetime 'a is picked by the caller, and there is no way a borrow within that function can be long enough. There would have been a quick fix to remove the 'a parameter on benchmark and replace it with a higher ranked trait bound (playground).
fn benchmark<U: AsRef<[u8]>, T: for<'a> AdapterTransaction<'a, U>>(txn: T)
In this example, 'a isn't chosen by the caller anymore, so the compiler is free to use a valid lifetime for the call.
As for the 2nd part of your question, traits can define associated types which can change depending on the implementation. You could have a trait that has an associated Output, which can change for each implemented type. There is a big difference with doing a generic param vs an associated type since in the former case, you are allowed to implement multiple generic variants of a trait for the same type. (This is how From<T> works, for example).
pub trait AdapterTransaction<'a> {
type Output;
fn get(&'a self, key: &[u8]) -> Option<Self::Output>;
}
impl<'a> AdapterTransaction<'a> for AdapterImpl {
type Output = &'a [u8];
fn get(&'a self, key: &[u8]) -> Option<Self::Output> {
Some(self.txn.get(&key))
}
}
fn benchmark<T>(txn: T)
where
for<'a> T: AdapterTransaction<'a>,
{
let _ = txn.get(&[]).unwrap();
}
Edit: Some of my initial assumptions weren't exact, it's not necessary to implement on &'a Type if the trait lifetime is used in a non-conflicting way.
Returning a reference to an owned field is allowed in other scenarios. For instance, the code below compiles fine.
struct Charlie<T> {
delta: T,
}
impl<T> Charlie<T> {
fn delta(&self) -> &T
{
&self.delta
}
}
I'd like to return a reference to an owned field from a trait method in similar fashion. Assume that Alpha is a trait in the standard library, so I can't modify it.
Is there a way to implement this?
trait Alpha {
type Item;
fn bravo(&mut self) -> Self::Item;
}
impl<T> Alpha for Charlie<T> {
type Item = &T;
fn bravo(&mut self) -> Self::Item
{
&self.delta
}
}
The above doesn't compile and the error messages don't seem to apply. If I try following the error message suggestions, things just get complicated and I run into a series of errors with suggestions that also don't pan out.
Compiling playground v0.0.1 (/playground)
error[E0106]: missing lifetime specifier
--> src/main.rs:22:17
|
22 | type Item = &T;
| ^ expected named lifetime parameter
|
help: consider introducing a named lifetime parameter
|
22 | type Item<'a> = &'a T;
| ^^^^ ^^^
I've tried the above suggestion and went so far as to declare 'a on Charlie's struct and add a PhantomData field, and thoroughly peppered the rest of my sources with 'as. And the compiler continues whining and complaining and nagging throughout the whole process.
I managed to find this post on StackOverflow, but the solutions there seem to all require modifying the trait.
I'm thinking what I'm trying to do may not be possible. But I don't really understand why not.
The Alpha trait method that I was struggling with is actually
type Item = Take<&I>;
fn next(&mut self) -> Option<Self::Item> { ... }
I was trying to return another wrapped iterator type, where I is the wrapped iterator. The Iterator trait itself doesn't define any lifetimes I can leverage.
I do know a way to implement around this limitation using the smart pointer classes to encapsulate the field I want to share in Charlie. Then make that my Item type. I was just hoping for something with less overhead.
The contract for that trait unfortunately doesn't allow it.
trait Alpha {
type Item;
fn bravo(&mut self) -> Self::Item;
}
This says that, if Self is an Alpha, then "there exists some single Self::Item which I can get from any &mut self with any lifetime". You want "there exists a class of Self::Item whose lifetimes relate to &mut self in a nontrivial way".
The easiest way to get around this is to make bravo take self by value.
trait Alpha {
type Item;
fn bravo(self) -> Self::Item;
}
Now, the contract says "there's some way to get a Self::Item from a self", which is much simpler. We can implement it as
impl<'a, T> Alpha for Charlie<&'a mut T> {
type Item = &'a T;
fn bravo(self) -> Self::Item {
&self.delta
}
}
This trait definition compiles fine:
trait Works {
fn foo() -> Self;
}
This, however, does lead to an error:
trait Errors {
fn foo() -> Option<Self>;
}
error[E0277]: the size for values of type `Self` cannot be known at compilation time
--> src/lib.rs:6:5
|
6 | fn foo() -> Option<Self>;
| ^^^^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
|
= help: the trait `std::marker::Sized` is not implemented for `Self`
= note: to learn more, visit <https://doc.rust-lang.org/book/second-edition/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait>
= help: consider adding a `where Self: std::marker::Sized` bound
= note: required by `std::option::Option`
With the : Sized supertrait bound, it works.
I know that the Self type in traits is not automatically bound to be Sized. And I understand that Option<Self> cannot be returned (via the stack) unless it is sized (which, in turn, requires Self to be sized). However, the same would go for Self as return type, right? It also cannot be stored on the stack unless it's sized.
Why doesn't the first trait definition already trigger that error?
(This question is related, but it doesn't answer my exact question – unless I didn't understand it.)
There are two sets of checks happening here, which is why the difference appears confusing.
Each type in the function signature is checked for validity. Option inherently requires T: Sized. A return type that doesn't require Sized is fine:
trait Works {
fn foo() -> Box<Self>;
}
The existing answer covers this well.
Any function with a body also checks that all of the parameters are Sized. Trait functions without a body do not have this check applied.
Why is this useful? Allowing unsized types to be used in trait methods is a key part of allowing by-value trait objects, a very useful feature. For example, FnOnce does not require that Self be Sized:
pub trait FnOnce<Args> {
type Output;
extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
}
fn call_it(f: Box<dyn FnOnce() -> i32>) -> i32 {
f()
}
fn main() {
println!("{}", call_it(Box::new(|| 42)));
}
A big thanks to pnkfelix and nikomatsakis for answering my questions on this topic.
It's in the error message:
= note: required by `std::option::Option`
Option requires the type to be Sized because it allocates on the stack. All type parameters to a concrete type definition are bound to Sized by default. Some types choose to opt out with a ?Sized bound but Option does not.
Why doesn't the first trait definition already trigger that error?
I think this is a conscious design decision, due to history, future-proofing and ergonomics.
First of all, Self is not assumed to be Sized in a trait definition because people are going to forget to write where Self: ?Sized, and those traits would be less useful. Letting traits be as flexible as possible by default is a sensible design philosophy; push errors to impls or make developers explicitly add constraints where they are needed.
With that in mind, imagine that trait definitions did not permit unsized types to be returned by a method. Every trait method that returns Self would have to also specify where Self: Sized. Apart from being a lot of visual noise, this would be bad for future language development: if unsized types are allowed to be returned in the future (e.g. to be used with placement-new), then all of these existing traits would be overly constrained.
The issue with Option is just the tip of the iceberg and the others have already explained that bit; I'd like to elaborate on your question in the comment:
is there a way I can implement fn foo() -> Self with Self not being
Sized? Because if there is no way to do that, I don't see the point in
allowing to return Self without a Sized bound.
That method indeed makes it impossible (at least currently) to utilize the trait as a trait object due to 2 issues:
Method has no receiver:
Methods that do not take a self parameter can't be called since there
won't be a way to get a pointer to the method table for them.
Method references the Self type in its arguments or return type:
This renders the trait not object-safe, i.e. it is impossible to create a trait object from it.
You are still perfectly able to use it for other things, though:
trait Works {
fn foo() -> Self;
}
#[derive(PartialEq, Debug)]
struct Foo;
impl Works for Foo {
fn foo() -> Self {
Foo
}
}
fn main() {
assert_eq!(Foo::foo(), Foo);
}
This code give me a compilation error:
trait IBoo {
fn new() -> Box<IBoo>;
}
while this code compiles without any error:
trait IBoo {
//fn new() -> Box<IBoo>;
}
trait IFoo {
fn new() -> Box<IBoo>;
}
Why does the first one not compile? rustc --explain E0038 does not give me a direct hint why it is not possible.
Is it possible to combine construction and methods in one interface (trait)?
This is from the description of E0038:
Method has no receiver
Methods that do not take a self parameter can't be called since
there won't be a way to get a pointer to the method table for them.
trait Foo {
fn foo() -> u8;
}
This could be called as <Foo as Foo>::foo(), which would not be able
to pick an implementation.
Adding a Self: Sized bound to these methods will generally make this
compile.
trait Foo {
fn foo() -> u8 where Self: Sized;
}
You can do this:
trait IBoo {
fn new() -> Box<IBoo>
where
Self: Sized;
}
In other cases, you can place the restriction on the entire impl:
The compiler tells you the exact reason this doesn't work:
error[E0038]: the trait `IBoo` cannot be made into an object
--> src/main.rs:2:5
|
2 | fn new() -> Box<IBoo>;
| ^^^^^^^^^^^^^^^^^^^^^^ the trait `IBoo` cannot be made into an object
|
= note: method `new` has no receiver
Note the last line. It's telling you that the reason for the error is that new() doesn't depend on having an instance of a value implementing the IBoo trait.
By not taking a pointer of some kind to self, the method cannot be invoked by dynamic dispatch. If it can't be invoked by dynamic dispatch, this means it cannot go into the trait's associated vtable. There has to be an associated vtable, because that's how something like Box<IBoo> works. Some time ago, the core Rust developers decided that including even a single "non-object safe" method in a trait disqualified the whole trait from being used as an object.
To put it in other words: because you've defined a method that cannot be dynamically dispatched, the IBoo trait as a whole is disqualified from being used with dynamic dispatch.
If you want some kind of constructor function, you need to have some other way of writing that. This could be using plain function pointers, or an IBooFactory trait, as you might with Java.