Idiomatic way to declare 2D, 3D vector types in Ru

2019-04-17 15:13发布

问题:

I'm looking to write some small library that use 2D and 3D points or directions in space
(vector in vector/matrix sense, not Rust's Vec).

Rust doesn't impose rules here, so you can make a tuple of floats, or a new struct with x, y, z members. or a single data: [f64; 3] member.

The reason I'd like to define a type here instead of using [f64; 3], is so I can declare methods such as length, normalized, also Add, Sub operators.

Whats a good basis to declare small 2D - 3D fixed sized data types on?


Note, while there are good existing libraries, I'd like to write my own since it only needs some basic operations, and I like to understand whats going on internally.

回答1:

This question is pretty broad and there isn't a clear best way to express what you want. It depends a lot on what you are planning to do with it.

I would propose a slightly different solution compared to the other answers: use a struct with x, y, z components and use strong typing to the full extent.


Vectors can be used to represent many things (points, colors, ...); you are talking about 2D and 3D points in space. The first thing to be aware of is the difference between point and direction vectors. Here is a great answer on math.stackexchange that explains the topic really well.

This difference can be reflected in the type system to catch logic errors. That's exactly what cgmath is doing. So I'd say that you actually want two types defined something like this:

struct Point3 {
    pub x: f32,
    pub y: f32,
    pub z: f32,
}
  • Why no tuple struct or array? The semantics of writing v.x are way clearer than v.0 or v[0]. The latter two cases are fitting if you have a vector type to represent arbitrary data. But as I mentioned, I think strong typing is a good choice here and we should choose good names accordingly.
  • Why pub? Why not? If you are talking about points or directions in space, there are no invalid vectors (ignoring the NaN float value here). So there isn't really a reason to restrict access to the fields.
  • Why f32? Good question... actually you should probably use a type parameter, but then you need to have some kind of trait bound ...

... well this brings me to my conclusion which you won't like: I think doing it correct and "idiomatic" requires some work in this case. And this work is properly done by libraries like cgmath. This cgmath library in particular has a very nice API design, IMO. Most functionality is implemented through traits (like VectorSpace) that reflect some of the maths behind it all. I, too, wanted to write vector types on my own for a project of mine, but I was eventually convinced to use a well tested, well designed library instead.


So how to do it "right"? Pretty much how cgmath does it:

  • strong typing
  • proper naming
  • having most functionality in abstract traits


回答2:

I would recommend defining a newtype, i.e. a tuple struct with a single member.

struct Vector3D([f64; 3]); // wraps an array
struct Vector3D((f64, f64, f64)); // wraps a 3-tuple

The fields of a tuple struct can be accessed by using their position (starting from zero) as the name of the field. For example, if you have a variable v of type Vector3D, v.0 would evaluate to the inner field. You can choose to make this field public or not; to make it public, add the keyword pub just before the name of the field's type.

struct Vector3D(pub [f64; 3]);

Note that this new Vector3D doesn't inherit any methods or traits from the wrapped type; it's up to you to provide whatever API you like on this type.



回答3:

Like Francis said, you can use a wrapper type, but if you don't need it to be truly distinct from the other types and to be able take advantage of an existing type's methods, you can use a type alias:

type Vector3D = (f64, f64, f64);

or

type Vector3D = [f64; 3];