Struct Region<S: Seek + Read> not a valid type definition?

I use fastnbt::anvil::Region read minecraft data.

I want to extend the API so I can iterate over units of data - so called "Chunks". Coming from a perl background Rust Iterator Traits seem somewhat confusing to me. I understand that the Iterator should be a separate struct.

But even in this step I stumble upon the type system:

use std::io::{Read, Seek};
pub struct Region<S: Seek + Read> {
    data: S,
}
pub struct RegionIter {
    region:  Region
    
}

(Playground)

This fails even to compile on line 6 with wrong number of type arguments: expected 1, found 0.

Could anybody point me in the right direction there? Isn't the "type" Region defined in Line 2?

Note that the type Region is generic. It takes another type as its argument, see how it says Region<S: Seek + Read>? This means that Region by itself is not actually a type yet. It needs one argument S that implements the traits Seek and Read and then Region<S> is a type, no matter what S is.

This is similar to how Vec is not a type, but for example Vec<i32> or Vec<String> are types.

2 Likes

You probably want something like this:

use std::io::{Read, Seek};
pub struct Region<S: Seek + Read> {
    data: S,
}
pub struct RegionIter<S: Seek + Read> {
    region:  Region<S>
    
}
1 Like

If you’re using Region from the crate you mentioned you probably don’t want to define it again in your own code. use is probably the right move there. Then your RegionIter type (maybe it should be called Chunks?) would need to also take a type parameter with the same bounds, and pass that type parameter along to the Region type it contains—as in @geebee22’s reply.

Also, unless you intend for your iterator to “use up” the region during iteration it may be better for it to hold a reference (something like &’r Region<S>) rather than taking ownership of it. But then your struct also has a lifetime parameter ’r and things become a little more complicated. So ignore that for now if you can and just let the iterator consume the original value.

Judging by how the library itself uses Region ⟶ see here it’s not trivial to iterate over it and also kind-of already implemented. Note how the function unnecessarily allocates a new vec about 1000 times per region. Note also that the whole thing is virtually uncommented. I would suggest looking for different NBT format crates, there seems to be a lot of them.

Edit: wait.. this is about anvil format, not NBT. Perhaps there is less diversity than I initially thought, still the fastnbt crate still hasn’t convinced me yet :laughing:

Edit2: Apparently anvil is using the NBT format? I never looked in to Minecraft world format on such a low level, perhaps the crate is kind-of decent. The only alternative I found was this one (which uses this crate).

Thank to all of you for the effort you put explaining. After skimming The Rust Book on the topic of Generics and Traits I did some further experimenting I ran into https://github.com/rust-lang/rust/issues/52662

@steffahn already pointed to the code in the fastnbt library for iterating over all the chunks in a region. The code used is somewhat complex to avoid unnecessary seeks while reading the chunks. Since the current Implementation in fastnbt can not be used by consumers of the library I was thinking "submit a PR with a nice iterator for the chunks".

But seems I have to learn a little bit more about Generics, Types and Iterators. Currently I have the newbie feeling all that strongly typed compile time checking ist mostly here to keep me form what could be done with two for loops and a yield in Python.

Are there any other rustic patterns to implement for_each_chunk()?

Background for the file format:
Minecraft Levels are stored in 16x16x256 “m” large “Chunks. These are encoded in NBT (think of BSON).

This NBT data is compressed and stored in 32x32 chunk large "Region: or Anvil files. This files are a bit like a Filesystem to handle empty and growing Chunks efficiently.

I thought this bit fiddling would be a good project for learning Rust.

Oh, did you already try writing your Trait<Foo: OtherTrait> that you ran into as Trait<Foo = impl OtherTrait> and check if it works?

It kind-of can. As long as you’re fine with just using this Chunk data type that actually only contains the information needed for drawing the chunk. Well, I guess it doesn’t, but for the sake of demonstration I’ll show you what I mean:

(untested code)

use fastnbt::anvil::{Region, draw};

fn for_each_chunk(r: Region<std::fs::File>, f: impl FnMut(usize, usize, &Chunk)) {
    struct Drawer<F>(F);
    impl<F: FnMut(usize, usize, &Chunk)> draw::RegionDrawer for Drawer<F> {
        fn draw(&mut self, xc_rel: usize, zc_rel: usize, chunk: &Chunk) {
            self.0(xc_rel, zc_rel, chunk)
        }
    }
    draw::parse_region(r, &mut Drawer(f))
}
1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.