7 ms·
Patterns with Rust Types
- heinrich5991 3y agoAs was already mentioned in the Reddit thread on /r/rust (https://old.reddit.com/r/rust/comments/wb10zi/patterns_with_rust_types/ii4iki2/ https://old.reddit.com/r/rust/comments/wb10zi/patterns_with_...): It's weird to take a platform-dependent integer for a database ID. Use `u32` or `u64` instead of `usize`.
- armchairhacker 3y agoI find `usize` in Rust is like the default unsigned integer, used for nearly everything and especially everything index-related. I do think in practice using `u64` instead of `usize` is meaningless, since there are so few 32-but systems today. The nice thing with the newtype pattern though is that if for whatever reason your program is going to run on a 32-bit instance and you need 64-bit IDs, it’s a 1-line change.
- heinrich5991 3y agoIt was explicitly renamed away from `uint` to make it clear that it's not the default integer. Rust tries to encourage using explicit bit widths.
- sshine 3y ago> there are so few 32-bit systems today Some of the most widely used MCUs for embedded Rust are 32-bit. But typically you want to be explicit about your sizes when doing embedded anyways. I think using usize is mostly a convenience that lets you fit values into standard library functions.
- saghm 3y agoI tend to take the opposite approach in my Rust code. Unless I have a specific reason to use `usize` (e.g. using a type to index a standard library type that requires it), I always try to pick an exact size. It's pretty rare that I run into something where I'm either completely fine limiting the size to 32 bits or explicitly need to support something larger than that. Moreover, I'd still have to consider what happens at the limits of the integer type even if I did use `usize` for more general cases to make sure that I properly handle overflow (generally by using saturating or checked arithmetic in my use cases). Coincidentally, using usize only for stuff like indexing vectors and slices makes it much more rare I end up needing to explicitly deal with overflow for it because any time I have to make something dynamically sized, I already need to ensure that I'm putting reasonable limits on how much I'm willing to allocate, and that limit is inevitably much smaller than usize::MAX.
- inferiorhuman 3y agoMoreover, I'd still have to consider what happens at the limits of the integer type even if I did use `usize` for more general cases to make sure that I properly handle overflow (generally by using saturating or checked arithmetic in my use cases). That's one of those things I don't particularly like about rust. The most terse means of converting between numeric types doesn't do any bounds checking. use std::convert::TryFrom; fn main() { let a = usize::MAX; let b = a as u32; println!("{a} == {b}"); let c = u32::try_from(a).expect("this will always fail"); println!("{a} == {c}"); }
- deleted 3y ago[deleted]
- gbin 3y agoI see usize as the "memory indexing type" for the current platform I run on, storing it or sending it over some middleware should be a rare fringe case IMHO.
- nmilo 3y agoThe no-foreign-traits-on-foreign-types rule and the terrible ergonomics behind the newtype "pattern" combine to make one of the worst development experiences I've ever seen in a language. It basically means you can only use one large library, ever. (With caveats.) For example, if I'm writing a game, I include a math library and pull in a Vec3 type, then add Vec3 as a member to all my objects, and then later I want to pull in a serialization library that defines, for ex, a Serializable trait, I can't, unless: 1. I write my own Vec3 class, not fun. 2. I write my own serialization library, could be fun depending on who you are but surely a waste of time. 3. I make a newtype, implement Serializable on it, find-and-replace all Vec3s with my own SerializableVec3, fix about 500 errors one by one by adding .0 everywhere, implement wrappers for all the Vec3 methods I want to use, rinse and repeat for every other foreign type I want to serialize. 4. Hope the math library and the serialization library know each other and the math library implements all the traits you need. In practice this is what happens, but this makes a weird situation where serde is the de facto standard/monopoly because it is literally impossible to use anything else. Which may be fine for now, serde is the standard precisely because it's so extensible, but if you ever want to use another library with traits, well you're SOL and it's back to step 3. How they managed to ship a language with dev ergonomics this bad is beyond me. Especially when the solution is so simple. Allow foreign traits on foreign types for executable projects only. Put it behind a compiler flag for all I care. -fallow-foreign-traits-I-dont-care-if-this-breaks-things. Just let me implement my damn traits, the current state is ridiculous.
- fiedzia 3y ago3. should be one click in your IDE.
- staunton 3y agoI strongly disagree that these are big problems in practice. There are macros you can use to make newtypes more ergonomic to use and reduce boilerplate, etc. The advantage it gives is forced adherence to many implicit assumptions of library authors, which means that it's a lot easier (read: preventing subtle bugs) to compose different libraries. Meanwhile, allowing it might split and completely break the ecosystem.
- pornel 3y agoI love that structs can be strictly non-copyable. This way I don't need to heap allocate objects to ensure their uniqueness. I can still modify them in place without worrying there will be any out-of-date copies that weren't updated. Or I can give out simple ints or even zero-sized structs that can be handles/tokens that grant exclusive or single-use access.
- monocasa 3y agoUnfortunately I haven't been able to find a way to make structs completely noncopyable except via Pin. The compiler is reference tracking, and if it's sure that there aren't references it'll copy the value around. So for instance full RVO isn't guaranteed, and you can catch the bit pattern of &self changing out from under you even in a struct that doesn't implement Copy. I'd kill for a proper copy constructor to cover the edge cases.
- hardwaregeek 3y agoBuilder patterns are also super helpful for libraries. Allows you to create APIs with lots of optional fields without having call sites that pass in a dozen None values. There's also the type state pattern where you have a struct take a generic so you can allow certain methods depending on the generic type. Like say you have a struct with a field that you want to be optional, but you know statically that at a certain point in the code it'll be set. You can do: struct User<IdType> { id: IdType } impl User<()> { fn create() -> User<u64> { ... } } impl User<u64> { fn id() -> u64 { user.id } } That way you can have some methods where you can be certain that the ID is set and some methods where it's not set.
- cowthulhu 3y agoDoes anyone know of any sites that list out articles like this by language? I always find it tough to locate more advanced or "meta" articles like this that aren't just explaining basic concepts.
- justincredible 3y ago[dead]