I try to implement approach for testing that was advised in that answer. I plan to use this approach in the GitHub - tafia/quick-xml: Rust high performance xml reader and writer project. Below the simplified code from quick-xml and test code that I try to write for https://github.com/mzabaluev/generic-tests:
use std::io::BufRead;
//---------------------------------------------------------------
// quick-xml code
trait BufferedInput<'b, B> {
fn read(&mut self, buf: B) -> &'b [u8];
}
/// Generic reader, copies data from reader into intermediate buffer
impl<'b, R: BufRead> BufferedInput<'b, &'b mut Vec<u8>> for R {
/// Read & Copy data from `self` into `buf`,
/// return data that borrow from `buf`
fn read(&mut self, buf: &'b mut Vec<u8>) -> &'b [u8] {
&buf[..]
}
}
/// Borrowed reader, zero-copy
impl<'i> BufferedInput<'i, ()> for &'i [u8] {
/// Directly borrow data from `self`, do not copy,
/// return data that borrow from `self`
fn read(&mut self, _buf: ()) -> &'i [u8] {
self
}
}
//---------------------------------------------------------------
// My tests
#[cfg(test)]
mod test {
trait Storage<'b>: Default {
type Buffer;
/// Returns buffer for `BufferedInput::read`
fn buffer(&'b mut self) -> Self::Buffer;
}
impl<'b> Storage<'b> for () {
type Buffer = ();
fn buffer(&'b mut self) {}
}
impl<'b> Storage<'b> for Vec<u8> {
type Buffer = &'b mut Vec<u8>;
fn buffer(&'b mut self) -> Self::Buffer {
self
}
}
// function for `generic-tests` crate
fn test<'b, S: 'b + Storage<'b>>()
where &'static [u8]: super::BufferedInput<'b, S::Buffer>,
{
use super::BufferedInput;
// Create storage
let mut storage = S::default();
// Get the buffer. Depending on the `S`,
// that buffer can borrow from `storage` or not
let buf = storage.buffer();
let mut input = b"".as_ref();
input.read(buf);
}
// functions to what `generic-tests` crate should expand `test` function
#[test]
fn buffered_test() {
test::<Vec<u8>>();
}
#[test]
fn borrowed_test() {
test::<()>();
}
}
But this code cannot compile because of strange error from borrow checker that I don't understand:
Compiling playground v0.0.1 (/playground)
error[E0597]: `storage` does not live long enough
--> src/lib.rs:59:15
|
49 | fn test<'b, S: 'b + Storage<'b>>()
| -- lifetime `'b` defined here
...
59 | let buf = storage.buffer();
| ^^^^^^^^^^^^^^^^
| |
| borrowed value does not live long enough
| argument requires that `storage` is borrowed for `'b`
...
63 | }
| - `storage` dropped here while still borrowed
For more information about this error, try `rustc --explain E0597`.
error: could not compile `playground` due to previous error
I don't understand why storage
cannot be dropped and who is borrowed it? Even if instead of using buf
in BufferedInput::read
I just drop()
it, nothing changed. I do not see any other borrows here. So what the borrow checker tries to say me here?
Also, I don't understand why I need the S: 'b + ...
bound in the line 49, but without it the following error is produced (Playground):
Compiling playground v0.0.1 (/playground)
error[E0309]: the parameter type `S` may not live long enough
--> src/lib.rs:59:15
|
49 | fn test<'b, S: Storage<'b>>()
| -- help: consider adding an explicit lifetime bound...: `S: 'b +`
...
59 | let buf = storage.buffer();
| ^^^^^^^ ...so that the type `S` is not borrowed for too long
error[E0309]: the parameter type `S` may not live long enough
--> src/lib.rs:59:23
|
49 | fn test<'b, S: Storage<'b>>()
| -- help: consider adding an explicit lifetime bound...: `S: 'b +`
...
59 | let buf = storage.buffer();
| ^^^^^^ ...so that the reference type `&'b mut S` does not outlive the data it points at
For more information about this error, try `rustc --explain E0309`.
error: could not compile `playground` due to 2 previous errors
warning: build failed, waiting for other jobs to finish...
error: build failed
If I understand Storage
trait correctly, the buffer()
method should return buffer with the same lifetime that storage
have and storage
lifetime should determine the final lifetime. Because this sounds like a candidate for HRTB, I tried to change test
bounds to:
// function for `generic-tests` crate
fn test<S>()
where for<'b> S: Storage<'b>,
&'static [u8]: super::BufferedInput<'b, S::Buffer>,
{
use super::BufferedInput;
// Create storage
let mut storage = S::default();
// Get the buffer. Depending on the `S`,
// that buffer can borrow from `storage` or not
let buf = storage.buffer();
let input = b"".as_ref();
input.read(buf);
drop(buf);
}
But it seems impossible to use one for<'b>
for both constraints, and using for<'b>
for each constraint independently produces another error (playground):
Compiling playground v0.0.1 (/playground)
error: implementation of `BufferedInput` is not general enough
--> src/lib.rs:73:5
|
73 | test::<()>();
| ^^^^^^^^^^ implementation of `BufferedInput` is not general enough
|
= note: `BufferedInput<'0, ()>` would have to be implemented for the type `&'static [u8]`, for any lifetime `'0`...
= note: ...but `BufferedInput<'1, ()>` is actually implemented for the type `&'1 [u8]`, for some specific lifetime `'1`
error: could not compile `playground` due to previous error
So I tried to move &'static [u8]: super::BufferedInput<'b, S::Buffer>
constraint into Storage
trait itself, but that also produces errors (at least, I understand them and they legitimate).
So my questings is: why the original errors are produced and is there any way to solve this problem?