My gamedever wishlist for Rust


#22

Instead of using Vec, you could consider using the arrayvec crate which gives you a Vec alternative with an array as backing storage.


#23

About cargo rebuilding the build script when any file gets edited:

This makes sense. I have made a program where the build script reads a specification and outputs code based on it. There is no wah for cargo to know this happens, so if any file changes the program should be recompiled.


#24

I proposed to make build scripts explicitely state the files they depend upon: https://github.com/rust-lang/cargo/issues/1162

I also tried implementing this myself at some point, but I just don’t understand Cargo’s code when it comes to this (acrichto says it’s easy, but it was too hard for me at least).

This is especially annoying when working on glium. All examples and tests use GLSL code which is checked only at runtime. If I make a mistake in the GLSL code and fix it, the whole library recompiles, which takes several minutes.


#25

I think moving the build script into a seperate crate would fix that.


#26

Do you think it would be worth starting from scratch with a new maths lib, or do you think that cgmath can be salvaged? If so, what do we need to do to get it up to scratch?


#27

I don’t know if it should be rewritten or not.

Basically, for the changes:

  • All the methods defined in traits (Matrix, Vector) should be moved in the objects themselves, similar to what the stdlib did.
  • All the mul_*, add_*, rem_*, etc. functions turned into Mul, Add, Rem, etc. trait implementations.
  • A function to create scale matrices (like this). For the moment I write let scale = { let mut m = Matrix4::identity().mul_s(scale); m[3][3] = 1.0; m }; every time I want to create such a matrix.
  • Functions that automatically create rotation and translation matrices without the need to go through a vector, a quaternion, an angle, or a Matrix2.
  • I have never used things such as Point2/Point3, Basis2/Basis3, Line, etc. It’s maybe “more correct” to use them, but I don’t want to learn a different API just for this. They are like manipulating types like Seconds, Meters, Kilograms instead of just integers or floats. Sure it’s more correct, but it’s also more annoying to deal with.

#28

As far as creating unsized types goes, if #[unstable] is on the table…

#![feature(unsize)]
use std::marker::{Unsize,PhantomData};
use std::ops::{Deref,DerefMut};

#[derive(Debug)]
struct Foo<T, U: ?Sized> { _phantom: PhantomData<T>, head: u64, tail: U }

impl<T, U: ?Sized+Unsize<[T]>> Deref for Foo<T, U> {
    type Target = Foo<T, [T]>;
    fn deref(&self) -> &Self::Target {
        self
    }
}

impl<T, U: ?Sized+Unsize<[T]>> DerefMut for Foo<T, U> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        self
    }
}

fn main() {
    let mut x: Foo<u8, [u8; 3]> = Foo {
        _phantom: PhantomData, head: 42, tail: [ 1, 2, 3 ]
    };
    println!("x = {:?}", &x);
    {
        let y: &Foo<u8, [u8]> = &x;
        println!("y = {:?}", y);
    }
    let mut z: &mut Foo<u8, [u8]> = &mut x;
    z.tail[1] = 17;
    println!("z = {:?}", z);
}

Output:

x = Foo { _phantom: PhantomData, head: 42, tail: [1, 2, 3] }
y = Foo { _phantom: PhantomData, head: 42, tail: [1, 2, 3] }
z = Foo { _phantom: PhantomData, head: 42, tail: [1, 17, 3] }
Program ended.

If you just want varying sizes of [u8], then you can even drop the T.

Could probably also use Borrow, for trickier shenanigans.

EDIT: In trying to implement Borrow<Foo<U>> for Foo<T> where T: Unsize<U>, I found that the stdlib already does so! This works, at least in nightly:

EDIT 2: Works in stable as well.

#[derive(Debug)]
struct Foo<T: ?Sized> { head: u64, tail: T }

fn main() {
    let mut x: Foo<[u8; 3]> = Foo { head: 42, tail: [ 1, 2, 3 ] };
    println!("x = {:?}", &x);
    {
        let y: &Foo<[u8]> = &x;
        println!("y = {:?}", y);
    }
    let mut z: &mut Foo<[u8]> = &mut x;
    z.tail[1] = 17;
    println!("z = {:?}", z);
}

That’s it. Nothing more.


#29

The problem is that removing the Sized bound from Copy is backwards incompatible.
I wanted this as well, but sadly it might not be possible (you could change the definition of Clone and do a crater run to check).

After the MIR is integrated with trans, and we have a good placement implementation story, there isn’t much stopping us from allowing box Foo { val1: 1.0, rest: [0; n] } with runtime variable n.
Definitely would be great to have this, maybe you could try to push a RFC through the process? We might need to slightly change the design of placement APIs to handle it.


#30

Just posted a PR for the cgmath operators. I don’t think I will retire the traits though. Matrix::from_diagonal was added in #234. I’ve also added conversions to/from tuples and fix sized arrays. I will look at retiring the matrix wrapper types, but will keep the Points, because I still feel there is value to be had there. I’m going to look at making more improvements/modernisations, so watch this space.


#31

Is that solved by https://github.com/Kimundi/owning-ref-rs ?

Does https://github.com/arcnmx/nue work for you? I haven’t used it myself, but I was excited to see it hit the scene since this is a problem that I’ve encountered too.

I’d be interested in extracting parts of Servo’s flexbox implementation if this works for you. The React Native folks were interested in this as well.

Servo used to have one of these in https://github.com/servo/rust-layers but it was removed when we no longer used it. It’s still in the history. It was pretty specialized to Servo’s needs though.

WebRender has a AABB tree implementation.


#32

I remember taking a look at this library back when it was announced.

Unfortunately I think it’s far too limited. You can only hold one reference to the boxed content simultaneously, and it can only be used with references (the map function takes a FnOnce(&T) -> &U, so you can’t return a Something<'a>).


#33

The post is quite old now. Since then I’ve started working on my own library.


#34

Actually, I did this in a fairly generic manner, see acacia. Admittedly, I have not done a lot to promote it. Does that fit what you’re looking for?

I’ll have to check how 1.0-compatible it is. And I should also note that I favoured genericity over performance, when implementing it, as getting both would have required HKT afair. I remember looking into ways around that, but haven’t achieved that before.


#35

I think this is something different. If I understand correctly, he is asking for references that can only ever be written to (potentially multiple times), but can never be read from. When interfacing with memory-mapped hardware, there are often memory regions which can be written to, but for which reading would result in a segmentation fault. In Rust, there is currently no way to statically enforce this property.

In contrast, my understanding of &out/&uninit references is that they would point to regions of memory that are initially uninitialized, but once written to can be read from and generally used similarly to mut.


#36

The real pointer would be hidden inside a struct. The struct’s API would only provide &out references.

In safe land, having &out would be useful to prevent the user from reading uninitialized data (to prevent logic errors, for example if the buffer contains sensitive data that isn’t cleared and that is then erroneously copied to a socket).

And in unsafe land, having &out would be useful to prevent againt segfaults caused by forbidden reads.


#37

I think a write-only type would be sufficient. With this, we could create a wrapper type that has a write-function which allows a FnMut to write to the contained write-only type and return a mutable slice over the written part of the buffer. That should be sufficient to handle the “uninitialized buffer” use case.


#38

How would this work? Given that all structs of the same type must have the same runtime size, you can’t have a in-place variable length array in the struct.

Given that the wrapper isn’t doing much apart from denying a part of the API, wouldn’t it get completely inlined by the optimizer, hence being reduced to zero cost?


#39

It is already possible to manipulate a Box<Foo> or a &Foo. The length of the array is stored in the pointer. The only problem is that there’s no syntax to create such a Box<Foo>.

I should have given more details about what I mean by this.

If you imagine a struct like this:

struct BufferContent {
    a: [u8; 512],
    b: [f32; 64],
}

Now let’s say that you have a buffer in video memory that contains a instance of BufferContent, and you map that buffer in order to modify the data.

The only way to remain generic in the library that provides the mapping is by using a template parameter:

pub struct Mapping<T: ?Sized> {
    ...
}

And if you provide only setters and no getters, it looks like this:

impl<T: ?Sized> Mapping<T> {
    pub fn set(value: &T) {
        memcpy(...)
    }
}

But having an API like this means that you need to write the whole content of a BufferContent at once, even if you just want to modify a small part of it. Even if you want to write the content of the entire buffer, you need to your BufferContent in RAM and then copy it, instead of directly writing to the mapped video memory.

Maybe I’m wrong and LLVM is capable of optimizing this, but that would be very surprising.


#40

Unless I’m mistaken, these are different from . Box<Foo> is a heap-allocated object, while the Box just keeps a constant sized pointer. The same goes for &Foo, which also ends up being a constant size pointer. On the other hand, [u8] doesn’t have its size known at compile time.

I’m probably misinterpretting this, but it seems that what you’re trying to do is wrap a buffer pointer in an API which prevents reading from it. Why do you need the Mapping<T> to be generic?


#41

In this case the API is OpenGL and the mapped data is the content of a buffer in video memory. The user chooses what’s inside the buffer. It’s usually a [Vertex] (where Vertex is user-defined), a [u16] or a UniformsList (user-defined as well).