Skip to content

feat-request(rust): support for getting offset for tables #8194

@cstrahan

Description

@cstrahan

We have a need for indexing into an unfinished message, something like so:

let mut map = HashMap::new();
for i in 0..children.len() {
    let child = children.get(i);
    // on the following line, get_offset!(child) gives us a WIPOffset<ViewProto<'_>>
    map.insert(Self::unique_id2(child), get_offset!(child));
}
// ... `map` is then used to create and push more values to `fbb`

where ViewProto is a table:

table ViewProto {
  // redacted
}

Our code currently does some unsound unsafe stuff allowing for our get_offset macro to construct the WIPOffset<ViewProto<'_>>. I'm currently in the process of trying to resolve our unsound code (and, more generally, remove as much use of unsafe as possible).

What I would like is something like this in the generated code:

pub struct ViewProto<'a> {
  pub _tab: flatbuffers::Table<'a>,
}

impl<'a> ViewProto<'a> {
    /// Return the offset into the FlatBufferBuilder
    pub fn offset<'bldr>(&self, fbb: &flatbuffers::FlatBufferBuilder<'bldr>) -> flatbuffers::WIPOffset<ViewProto<'bldr>> {
        // TODO: maybe have a debug assert here that sanity checks that
        // self._tab.buf is the same as fbb.unfinished_data().
        flatbuffers::WIPOffset::new((self._tab.buf.len() - self._tab.loc) as flatbuffers::UOffsetT)
    }
}

The challenge I'm trying to overcome here is that the 'a lifetime in something like ViewProto<'a> represents the lifetime of the underlying buffer, while 'bldr in FlatBufferBuilder<'bldr> is (as I understand it) is there to avoid using offsets from one FBB with another FBB. But, going from WIPOffset<ViewProto<'bldr> to ViewProto<'a> (where 'a is the lifetime of our self.fbb) loses the original 'bldr lifetime. So my idea was to have something that would be both convenient and more type safe than inlining the flatbuffers::WIPOffset::new((self._tab.buf.len() - self._tab.loc) as flatbuffers::UOffsetT) everywhere -- at least the offset function ensures that given a T we end up with a WIPOffset<T>, whereas the aforementioned expression will happily give a WIPOffset<U> (for some other unintended type U).

With the above in place, the code at the start of this issue could look like:

let mut map = HashMap::new();
for i in 0..children.len() {
    let child = children.get(i);
    map.insert(Self::unique_id2(child), child.offset(&self.fbb));
}
// ... `map` is then used to create and push more values to `fbb`

Would something along these lines be accepted?

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions