-
Notifications
You must be signed in to change notification settings - Fork 21
Limitations of the Matcher Trait with Proxy reference type #323
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
I fiddled with the playground and got things to compile: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=c9f86ed89ac8cf349a590f089b99b632 I needed to add a lifetime parameter to the |
Nice find. I had a solution but with more lifetime parameters and clunkier. The real world example was in:
|
At the same time, the
This is equivalent to
The signature claims that the returned
|
To be completely honest, I may have butchered the actual meaning of the protobuffer library in my minimal example. The |
Okay, I understand now. The Is there perhaps a way to avoid the reborrow? Can you show how you are building a matcher against the protobuf library? |
I just ran into the same problem when trying to add a matcher for byte sequences. See #327. The good news is that the example is much simpler. Perhaps this justifies adding a lifetime parameter to |
I've tried adding a lifetime parameter to See the result on this playground. Here I try the approach out with the I have also tried switching the |
I managed to get some success on the displays_as with an existential lifetime (which should work here, since However, this is probably tricky to generalize to fully generic matchers. |
Yeah, I tried using the
(This requires a lot of the library, so it's hard to put in a playground unfortunately.) |
Hmmm, yeah, that would need a bit of extra work. I will try to create a branch with only a few matcher implementations so it is easy to iterate over different trait definitions. I will add also the playground example of the This will make it easier to share attempts of new trait definition and avoid the limitation of the playground. |
I played around with
in this branch I believe the problem is that the trait is too flexible. You could implement a matcher that would store I think:
(working on it in this branch) or
(but that probably does not compile) |
I had tried your first proposal but didn't get far. I think your second proposal has the right idea. It almost compiles; see playground. The error I get is that putting the reference on an associated type is not considered to constrain the lifetime parameter. This is a known issue in Rust. If I replace the associated type with a type parameter, it does compile. See playground. Of course, we had a good reason to switch to using an associated type, so I don't think that's the answer. Rather it would be great to fix the underlying issue in rustc. |
After lots of playing around with lifetimes, I did find a set of incantations which works in the case I linked above. See playground. This is based on your second proposal, but has the twist that the Let me see whether this can be generalised. |
The first proposal seems to avoid the issue of associated type constrains. Playground and branch. However, I am still blocked on it when calling the matcher. This is my error:
|
My current last attempt is to move back to generic parameters with this new matcher interface:
I was expecting that this will cost us the support for I will work more on it, after a few PR reviews. |
(referencing conversation in #336 ) However, you can't have your cake and eat it. Consider this test. The compiler rejects this code with:
With the Moreover, matchers cannot be returned by Hence, continuing with this approach will require to:
I am still not convince this approach is the best (or whether it would work). |
The approach described above does work, but sacrifices I tried to look back at the The simplest reproducible examples are: Of course, removing the explicit
Somehow, the type checker is only confused on picking which But, the type checker shouldn't need to decide since Another approach is to rely on impl trait in return to tip the type checker in the right direction. But I did not find a working solution. |
I have finished one last experimentation and it seems that we have the following options: #336 which migrate back to generic parameter, but trade automatically injecting The trade can be handled by:
#353 which keeps associated types but let constrain the lifetime of Both solutions make heavy use of HRTB which require to make the return types explicit (not through |
Previously, `IsEncodedStringMatcher` converted its actual value input to a `Vec` and constructed a `String` out of that in order to match it. This is because using a string slice caused problems with the borrow checker as described in #323. The fundamental problem is that the type against which the inner matcher is matching is a reference whose lifetime must be named in the trait bound: ``` impl<ActualT: AsRef<[u8]> + Debug, InnerMatcherT> Matcher for IsEncodedStringMatcher<ActualT, InnerMatcherT> where InnerMatcherT: Matcher<ActualT = &'what str>, ^^^^ What goes here??? ``` One option is just to remove the reference: ``` impl<ActualT: AsRef<[u8]> + Debug, InnerMatcherT> Matcher for IsEncodedStringMatcher<ActualT, InnerMatcherT> where InnerMatcherT: Matcher<ActualT = str>, ``` This doesn't work when the inner matcher is `eq`, since one would then be comparing an `str` and a string slice: ``` verify_that!("A string".as_bytes(), is_utf8_string(eq("A string"))) // Compile error: cannot compare str and &str ``` However, this problem does not occur with `StrMatcher`: ``` verify_that!("A string".as_bytes(), is_utf8_string(constains_substring("A string"))) // Passes ``` So this also introduces an easy way to obtain a `StrMatcher` to check string equality: ``` verify_that!("A string".as_bytes(), is_utf8_string(eq_str("A string"))) // Passes ``` This is slightly inconvenient, since one must use a different matcher to compare string equality in this case. But it is well-documented and not too inconvenient to use. So it should be okay.
Previously, `IsEncodedStringMatcher` converted its actual value input to a `Vec` and constructed a `String` out of that in order to match it. This is because using a string slice caused problems with the borrow checker as described in #323. The fundamental problem is that the type against which the inner matcher is matching is a reference whose lifetime must be named in the trait bound: ``` impl<ActualT: AsRef<[u8]> + Debug, InnerMatcherT> Matcher for IsEncodedStringMatcher<ActualT, InnerMatcherT> where InnerMatcherT: Matcher<ActualT = &'what str>, ^^^^ What goes here??? ``` One option is just to remove the reference: ``` impl<ActualT: AsRef<[u8]> + Debug, InnerMatcherT> Matcher for IsEncodedStringMatcher<ActualT, InnerMatcherT> where InnerMatcherT: Matcher<ActualT = str>, ``` This doesn't work when the inner matcher is `eq`, since one would then be comparing an `str` and a string slice: ``` verify_that!("A string".as_bytes(), is_utf8_string(eq("A string"))) // Compile error: cannot compare str and &str ``` However, this problem does not occur with `StrMatcher`: ``` verify_that!("A string".as_bytes(), is_utf8_string(constains_substring("A string"))) // Passes ``` So this also introduces an easy way to obtain a `StrMatcher` to check string equality: ``` verify_that!("A string".as_bytes(), is_utf8_string(eq_str("A string"))) // Passes ``` This is slightly inconvenient, since one must use a different matcher to compare string equality in this case. But it is well-documented and not too inconvenient to use. So it should be okay.
I've given this problem a bit more thought and looked more closely at the code in the protobuf library. The basic problem occurs when one needs an inner matcher whose
But that lifetime is defined by the function The second option doesn't work because the type of the inner matcher is already fixed. So the lifetime of In the case of The idea of removing the reference to the actual value (and therefore requiring that that actual value be
This will make the library much harder to understand and reason about. I wonder whether there is another solution which is somewhere between the current state and #367. Namely, perhaps I've found a solution for the problem with |
This allows the use of smart pointers for the actual value when invoking `Matcher::matches` and `Matcher::explain_match`. The Protobuf implementation namely uses a [kind of smart pointer](https://github.com/protocolbuffers/protobuf/blob/62d5d9bf67d27d3b0084dabcf67bff2f8238162b/rust/proxied.rs#L113). The lifetime on that smart pointer has no a priori relation to that of the parameter `actual`, causing the borrow checker to complain whenever invoking an inner matcher on the output of `as_view`. This commit addresses the problem by allowing the `ViewProxy` -- rather than a reference to it -- to be passed directly to `Matcher::matches` and `Matcher::explain_match`. It does this by making those two methods generic with respect to the input type. They must only implement `Deref<Target = Self::ActualT>`. The new test `googletest/tests/arena_test.rs` illustrates this with a simplified example. Introducing these generics unfortunately makes `Matcher` no longer object-safe. Since several matchers construct trait objts on `Matcher`, this also introduces an internal-only wrapper trait `ObjectSafeMatcher` which every `Matcher` auto-implements. While this makes some changes to how `Matcher` is defined and implemented, it should have no effect on how it is _used_, other than to fix the aforementioned problem. So it should not affect compatibility for most users of the crate. Fixes #323.
I've done some more experimenting and may have found a solution. See this commit. It works on the example of the playground to which you linked, with the addition that the That said, I'm wondering what kind of API you envision for matching against protos. Do you envision something like 'matches_pattern!`, based on a kind of property matcher? I'm not sure that the any of the approaches discussed here can work for that. Namely, the property matcher is based on an extractor closure which would in this case return a At the same time, it seems that an approach requiring the creation of a matcher for each field in the proto, as with I'd suggest first clarifying exactly what kind of API you want to see for matching protos, then solving directly for that desired API. |
Previously, `IsEncodedStringMatcher` converted its actual value input to a `Vec` and constructed a `String` out of that in order to match it. This is because using a string slice caused problems with the borrow checker as described in #323. The fundamental problem is that the type against which the inner matcher is matching is a reference whose lifetime must be named in the trait bound: ``` impl<ActualT: AsRef<[u8]> + Debug, InnerMatcherT> Matcher for IsEncodedStringMatcher<ActualT, InnerMatcherT> where InnerMatcherT: Matcher<ActualT = &'what str>, ^^^^ What goes here??? ``` One option is just to remove the reference: ``` impl<ActualT: AsRef<[u8]> + Debug, InnerMatcherT> Matcher for IsEncodedStringMatcher<ActualT, InnerMatcherT> where InnerMatcherT: Matcher<ActualT = str>, ``` This doesn't work when the inner matcher is `eq`, since one would then be comparing an `str` and a string slice: ``` verify_that!("A string".as_bytes(), is_utf8_string(eq("A string"))) // Compile error: cannot compare str and &str ``` However, this problem does not occur with `StrMatcher`: ``` verify_that!("A string".as_bytes(), is_utf8_string(constains_substring("A string"))) // Passes ``` So this also introduces an easy way to obtain a `StrMatcher` to check string equality: ``` verify_that!("A string".as_bytes(), is_utf8_string(eq_str("A string"))) // Passes ``` This is slightly inconvenient, since one must use a different matcher to compare string equality in this case. But it is well-documented and not too inconvenient to use. So it should be okay.
This allows the use of smart pointers for the actual value when invoking `Matcher::matches` and `Matcher::explain_match`. The Protobuf implementation namely uses a [kind of smart pointer](https://github.com/protocolbuffers/protobuf/blob/62d5d9bf67d27d3b0084dabcf67bff2f8238162b/rust/proxied.rs#L113). The lifetime on that smart pointer has no a priori relation to that of the parameter `actual`, causing the borrow checker to complain whenever invoking an inner matcher on the output of `as_view`. This commit addresses the problem by allowing the `ViewProxy` -- rather than a reference to it -- to be passed directly to `Matcher::matches` and `Matcher::explain_match`. It does this by making those two methods generic with respect to the input type. They must only implement `Deref<Target = Self::ActualT>`. The new test `googletest/tests/arena_test.rs` illustrates this with a simplified example. Introducing these generics unfortunately makes `Matcher` no longer object-safe. Since several matchers construct trait objts on `Matcher`, this also introduces an internal-only wrapper trait `ObjectSafeMatcher` which every `Matcher` auto-implements. While this makes some changes to how `Matcher` is defined and implemented, it should have no effect on how it is _used_, other than to fix the aforementioned problem. So it should not affect compatibility for most users of the crate. Fixes #323.
Previously, `IsEncodedStringMatcher` converted its actual value input to a `Vec` and constructed a `String` out of that in order to match it. This is because using a string slice caused problems with the borrow checker as described in #323. The fundamental problem is that the type against which the inner matcher is matching is a reference whose lifetime must be named in the trait bound: ``` impl<ActualT: AsRef<[u8]> + Debug, InnerMatcherT> Matcher for IsEncodedStringMatcher<ActualT, InnerMatcherT> where InnerMatcherT: Matcher<ActualT = &'what str>, ^^^^ What goes here??? ``` One option is just to remove the reference: ``` impl<ActualT: AsRef<[u8]> + Debug, InnerMatcherT> Matcher for IsEncodedStringMatcher<ActualT, InnerMatcherT> where InnerMatcherT: Matcher<ActualT = str>, ``` This doesn't work when the inner matcher is `eq`, since one would then be comparing an `str` and a string slice: ``` verify_that!("A string".as_bytes(), is_utf8_string(eq("A string"))) // Compile error: cannot compare str and &str ``` However, this problem does not occur with `StrMatcher`: ``` verify_that!("A string".as_bytes(), is_utf8_string(constains_substring("A string"))) // Passes ``` So this also introduces an easy way to obtain a `StrMatcher` to check string equality: ``` verify_that!("A string".as_bytes(), is_utf8_string(eq_str("A string"))) // Passes ``` This is slightly inconvenient, since one must use a different matcher to compare string equality in this case. But it is well-documented and not too inconvenient to use. So it should be okay. The introduction of `eq_str` also allows fixing a longstanding limitation of the matcher `property!` -- that it could not match on properties returning string slices. This PR therefore also updates the documentation accordingly and adds an appropriate test.
This allows the use of smart pointers for the actual value when invoking `Matcher::matches` and `Matcher::explain_match`. The Protobuf implementation namely uses a [kind of smart pointer](https://github.com/protocolbuffers/protobuf/blob/62d5d9bf67d27d3b0084dabcf67bff2f8238162b/rust/proxied.rs#L113). The lifetime on that smart pointer has no a priori relation to that of the parameter `actual`, causing the borrow checker to complain whenever invoking an inner matcher on the output of `as_view`. This commit addresses the problem by allowing the `ViewProxy` -- rather than a reference to it -- to be passed directly to `Matcher::matches` and `Matcher::explain_match`. It does this by making those two methods generic with respect to the input type. They must only implement `Deref<Target = Self::ActualT>`. The new test `googletest/tests/arena_test.rs` illustrates this with a simplified example. Introducing these generics unfortunately makes `Matcher` no longer object-safe. Since several matchers construct trait objts on `Matcher`, this also introduces an internal-only wrapper trait `ObjectSafeMatcher` which every `Matcher` auto-implements. While this makes some changes to how `Matcher` is defined and implemented, it should have no effect on how it is _used_, other than to fix the aforementioned problem. So it should not affect compatibility for most users of the crate. Fixes #323.
Previously, `IsEncodedStringMatcher` converted its actual value input to a `Vec` and constructed a `String` out of that in order to match it. This is because using a string slice caused problems with the borrow checker as described in #323. The fundamental problem is that the type against which the inner matcher is matching is a reference whose lifetime must be named in the trait bound: ``` impl<ActualT: AsRef<[u8]> + Debug, InnerMatcherT> Matcher for IsEncodedStringMatcher<ActualT, InnerMatcherT> where InnerMatcherT: Matcher<ActualT = &'what str>, ^^^^ What goes here??? ``` One option is just to remove the reference: ``` impl<ActualT: AsRef<[u8]> + Debug, InnerMatcherT> Matcher for IsEncodedStringMatcher<ActualT, InnerMatcherT> where InnerMatcherT: Matcher<ActualT = str>, ``` This doesn't work when the inner matcher is `eq`, since one would then be comparing an `str` and a string slice: ``` verify_that!("A string".as_bytes(), is_utf8_string(eq("A string"))) // Compile error: cannot compare str and &str ``` However, this problem does not occur with `StrMatcher`: ``` verify_that!("A string".as_bytes(), is_utf8_string(constains_substring("A string"))) // Passes ``` So this also introduces an easy way to obtain a `StrMatcher` to check string equality: ``` verify_that!("A string".as_bytes(), is_utf8_string(eq_str("A string"))) // Passes ``` This is slightly inconvenient, since one must use a different matcher to compare string equality in this case. But it is well-documented and not too inconvenient to use. So it should be okay. The introduction of `eq_str` also allows fixing a longstanding limitation of the matcher `property!` -- that it could not match on properties returning string slices. This PR therefore also updates the documentation accordingly and adds an appropriate test.
This allows the use of smart pointers for the actual value when invoking `Matcher::matches` and `Matcher::explain_match`. The Protobuf implementation namely uses a [kind of smart pointer](https://github.com/protocolbuffers/protobuf/blob/62d5d9bf67d27d3b0084dabcf67bff2f8238162b/rust/proxied.rs#L113). The lifetime on that smart pointer has no a priori relation to that of the parameter `actual`, causing the borrow checker to complain whenever invoking an inner matcher on the output of `as_view`. This commit addresses the problem by allowing the `ViewProxy` -- rather than a reference to it -- to be passed directly to `Matcher::matches` and `Matcher::explain_match`. It does this by making those two methods generic with respect to the input type. They must only implement `Deref<Target = Self::ActualT>`. The new test `googletest/tests/arena_test.rs` illustrates this with a simplified example. Introducing these generics unfortunately makes `Matcher` no longer object-safe. Since several matchers construct trait objts on `Matcher`, this also introduces an internal-only wrapper trait `ObjectSafeMatcher` which every `Matcher` auto-implements. While this makes some changes to how `Matcher` is defined and implemented, it should have no effect on how it is _used_, other than to fix the aforementioned problem. So it should not affect compatibility for most users of the crate. Fixes #323.
See this playground for a minimal example.
While implementing matchers for the Rust protobuf library, I stumbled on an issue with the lifetime requirement of the Matcher trait. The lifetime issue still did not click right in my head, so forgive me if what I say is not exactly what is happening. But, as far as I currently understand it, the
Matcher
trait accepts&ActualT
with any lifetime even thoughActualT
may be bounded, which makes theimpl Matcher
not able to access most functions ofActualT
.I see a few options to solve this issue:
&ActualT
to have a lifetime smaller thanActualT
.ActualT
and not&ActualT
in matcher, and constraintActualT
withCopy
Matcher
trait is just not right.This reminds me of the issue we currently have with
property!()
and string slices as it was also a reference lifetime issue.I will add more details while debugging further.
Current leads:
Matcher
trait and see where it takes usCopy
and see where it takes usget_a_field()
in the example from'_
to'a
seems to work, so maybe there is an issue in the libraryThe text was updated successfully, but these errors were encountered: