Skip to content
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

Field-level map_stream fails to compile #197

Closed
MrNbaYoh opened this issue Apr 6, 2023 · 3 comments
Closed

Field-level map_stream fails to compile #197

MrNbaYoh opened this issue Apr 6, 2023 · 3 comments
Assignees

Comments

@MrNbaYoh
Copy link
Contributor

MrNbaYoh commented Apr 6, 2023

So I was playing around with map_stream and it seems that this simple example fails to compile:

#[binread]
struct Test {
    #[br(map_stream = |r| r)]
    flags: u32
}

It gives:

cannot borrow `*__binrw_generated_var_reader` as mutable more than once at a time

If you look at the output of cargo-expand:

let __binrw_generated_reader_flags = &mut binrw::__private::map_reader_type_hint::<
    R,
    _,
    _,
>(|r| r)(__binrw_generated_var_reader); // <-------------------------------- first mutable borrow
let __binrw_generated_read_function = binrw::BinRead::read_options;
let mut flags: u32 = __binrw_generated_read_function(
        __binrw_generated_reader_flags,
        __binrw_generated_var_endian,
        <_ as binrw::__private::Required>::args(),
    )
    .map_err([...])?;
let __binrw_generated_saved_position = binrw::io::Seek::stream_position(
    __binrw_generated_var_reader,   // <------------------------------------- try to borrow as mutable
)?;
binrw::BinRead::after_parse(
    &mut flags,
    __binrw_generated_reader_flags, // <------------------------------------- second use of first borrow
    __binrw_generated_var_endian,
    <_ as binrw::__private::Required>::args(),
)?;
@MrNbaYoh MrNbaYoh changed the title Field-level map_stream does not work Field-level map_stream fails to compile Apr 6, 2023
csnover added a commit to csnover/binrw that referenced this issue Jul 2, 2023
This trait method existed only to support deferred reads of
`FilePtr` values but the requirement to have cloneable arguments
everywhere just to support this one use case is not justified by
the number of things that it breaks. Alternative designs that do
not break move-semantics are preferable, like reading offsets
into a `Vec<T>` and then using `args_iter` to resolve them all
at once instead.

Closes jam1garner#17, jam1garner#119.
Fixes jam1garner#185, jam1garner#197.
csnover added a commit to csnover/binrw that referenced this issue Jul 2, 2023
This trait method existed only to support deferred reads of
`FilePtr` values but the requirement to have cloneable arguments
everywhere just to support this one use case is not justified by
the number of things that it breaks. Alternative designs that do
not break move-semantics are preferable, like reading offsets
into a `Vec<T>` and then using `args_iter` to resolve them all
at once instead.

Closes jam1garner#17, jam1garner#119.
Fixes jam1garner#185, jam1garner#197.
csnover added a commit to csnover/binrw that referenced this issue Jul 2, 2023
This trait method exists pretty much exclusively to support
deferred reads of `FilePtr` values but causes ongoing trouble
elsewhere and the benefits do not seem to outweight the
problems:

1. It requires `T::Args` to be cloneable in many cases where it
   should not be the case, which causes confusion, has a strong
   chance of causing accidental slowness, and makes it
   unnecessarily hard to move from imports;
2. An analysis I ran of binrw users on GitHub showed that pretty
   much all cases of `FilePtr` were using `FilePtr::parse` or
   `deref_now`, so any potential performance benefit does not
   seem to be realised by real-world projects;
3. Because there is no hard requirement to call `after_parse`
   and it mostly will not break anything, it is too easy for
   users to write custom implementations that do not do this
   and so are subtly broken. From the same GH analysis, there
   was only one case where I found someone who wrote a custom
   implementation that correctly called `after_parse`;
4. Since `after_parse` does not build a stack of objects to
   revisit later, its ability to avoid non-linear reads of data
   is limited to at most one struct or enum at a time anyway.

Given these things (and probably others that I forget), IMO
the existence of this feature is not justified. Instead, I
think that a design that reads offsets into a `Vec<{integer}>`
and then iterates over them later to convert into `Vec<T>` is
preferable; a subsequent patch includes some helper functions
to do this, but also right now it can be done (with some verbosity)
using the `args_iter` helper.

Closes jam1garner#17, jam1garner#119.
Fixes jam1garner#185, jam1garner#197.
@csnover csnover self-assigned this Jul 10, 2023
csnover added a commit to csnover/binrw that referenced this issue Sep 18, 2023
This trait method exists pretty much exclusively to support
deferred reads of `FilePtr` values but causes ongoing trouble
elsewhere and the benefits do not seem to outweight the
problems:

1. It requires `T::Args` to be cloneable in many cases where it
   should not be the case, which causes confusion, has a strong
   chance of causing accidental slowness, and makes it
   unnecessarily hard to move from imports;
2. An analysis I ran of binrw users on GitHub showed that pretty
   much all cases of `FilePtr` were using `FilePtr::parse` or
   `deref_now`, so any potential performance benefit does not
   seem to be realised by real-world projects;
3. Because there is no hard requirement to call `after_parse`
   and it mostly will not break anything, it is too easy for
   users to write custom implementations that do not do this
   and so are subtly broken. From the same GH analysis, there
   was only one case where I found someone who wrote a custom
   implementation that correctly called `after_parse`;
4. Since `after_parse` does not build a stack of objects to
   revisit later, its ability to avoid non-linear reads of data
   is limited to at most one struct or enum at a time anyway.

Given these things (and probably others that I forget), IMO
the existence of this feature is not justified. Instead, I
think that a design that reads offsets into a `Vec<{integer}>`
and then iterates over them later to convert into `Vec<T>` is
preferable; a subsequent patch includes some helper functions
to do this, but also right now it can be done (with some verbosity)
using the `args_iter` helper.

Closes jam1garner#17, jam1garner#119.
Fixes jam1garner#185, jam1garner#197.
csnover added a commit to csnover/binrw that referenced this issue Sep 19, 2023
This trait method exists pretty much exclusively to support
deferred reads of `FilePtr` values but causes ongoing trouble
elsewhere and the benefits do not seem to outweight the
problems:

1. It requires `T::Args` to be cloneable in many cases where it
   should not be the case, which causes confusion, has a strong
   chance of causing accidental slowness, and makes it
   unnecessarily hard to move from imports;
2. An analysis I ran of binrw users on GitHub showed that pretty
   much all cases of `FilePtr` were using `FilePtr::parse` or
   `deref_now`, so any potential performance benefit does not
   seem to be realised by real-world projects;
3. Because there is no hard requirement to call `after_parse`
   and it mostly will not break anything, it is too easy for
   users to write custom implementations that do not do this
   and so are subtly broken. From the same GH analysis, there
   was only one case where I found someone who wrote a custom
   implementation that correctly called `after_parse`;
4. Since `after_parse` does not build a stack of objects to
   revisit later, its ability to avoid non-linear reads of data
   is limited to at most one struct or enum at a time anyway.

Given these things (and probably others that I forget), IMO
the existence of this feature is not justified. Instead, I
think that a design that reads offsets into a `Vec<{integer}>`
and then iterates over them later to convert into `Vec<T>` is
preferable; a subsequent patch includes some helper functions
to do this, but also right now it can be done (with some verbosity)
using the `args_iter` helper.

Closes jam1garner#17, jam1garner#119.
Fixes jam1garner#185, jam1garner#197.
csnover added a commit to csnover/binrw that referenced this issue Sep 21, 2023
This trait method exists pretty much exclusively to support
deferred reads of `FilePtr` values but causes ongoing trouble
elsewhere and the benefits do not seem to outweight the
problems:

1. It requires `T::Args` to be cloneable in many cases where it
   should not be the case, which causes confusion, has a strong
   chance of causing accidental slowness, and makes it
   unnecessarily hard to move from imports;
2. An analysis I ran of binrw users on GitHub showed that pretty
   much all cases of `FilePtr` were using `FilePtr::parse` or
   `deref_now`, so any potential performance benefit does not
   seem to be realised by real-world projects;
3. Because there is no hard requirement to call `after_parse`
   and it mostly will not break anything, it is too easy for
   users to write custom implementations that do not do this
   and so are subtly broken. From the same GH analysis, there
   was only one case where I found someone who wrote a custom
   implementation that correctly called `after_parse`;
4. Since `after_parse` does not build a stack of objects to
   revisit later, its ability to avoid non-linear reads of data
   is limited to at most one struct or enum at a time anyway.

Given these things (and probably others that I forget), IMO
the existence of this feature is not justified. Instead, I
think that a design that reads offsets into a `Vec<{integer}>`
and then iterates over them later to convert into `Vec<T>` is
preferable; a subsequent patch includes some helper functions
to do this, but also right now it can be done (with some verbosity)
using the `args_iter` helper.

Closes jam1garner#17, jam1garner#119.
Fixes jam1garner#185, jam1garner#197.
@csnover
Copy link
Collaborator

csnover commented Sep 21, 2023

This is fixed by 13525e3.

@csnover csnover closed this as completed Sep 21, 2023
@super-continent
Copy link

super-continent commented Sep 22, 2023

the following code still seems to break for me on 0.12. seems like this may be a different issue? I assume it breaks because of the closure from map_stream capturing the outer c which is a mutable reference when it could be copied/cloned or shared?

#[binread]
pub struct TestStruct {
    c: u32,
    #[br(count = c, map_stream = |a| a)]
    vec: Vec<u8>,
}

@csnover
Copy link
Collaborator

csnover commented Oct 25, 2023

@super-continent Sorry about the delay. What you are reporting was fixed by 97ce5be and will be working correctly in 0.13. For the moment you can switch to a git checkout to get the fix. A new release should happen soon.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants