Yet another library for handling endian in Rust. We use Rust's type system to ensure correct conversions and build data types with explicit endianness defined for more seamless portability of data structures between processor types.
Yes, there are several. But I'm not entirely happy with any of them. Specifically, most of the libraries out there right now focus on providing functions for doing endian conversions. Here are a few of them:
byteorder has over 11 million downloads, and is clearly the prevailing way to handle endian in Rust. However, it relies on programmers writing specific logic to swap bytes and requires accessing memory in ways that are unlike normal patterns for Rust. But really, the only difference between a big- and little-endian value is the interpretation. It shouldn't require a drastically different pattern of code to access them.
Because I think a better approach is to define your endianness as part of your data definition rather than in the logic of your program, and then to make byte order swaps as transparent as possible while still ensuring correctness. And because the more like normal Rust data types and operations this is, the more likely it is that people will write portable code and data structures in the first place.
The philosophy of this crate is that you define your endian when you write your data structures, and then you use clear, imperative logic to mutate it without needing to think about the details or the host endian. This makes it fundamentally different from crates that just give you a way to read a &[u8; 8] into a u64.
```Rust use simple_endian::*;
let foo: u64be = 4.into();
println!("raw: {:x}, value: {:x}", foo.raw(), foo); ```
The output will depend on what sort of computer you're using. If you're running a little-endian system, such as x86 (PCs, Macs, etc.), you will see the raw in big endian representation, as it's stored in memory. Note that the raw method is mostly there for debugging purposes, and should not be used often.
This works in reverse as well: ```Rust use simple_endian::*;
let foo: u64be = 4.into(); let bar = u64::from(foo);
println!("value: {:x}", bar); ```
If you prefer, there's a convenience method so that you don't need to explicitly convert back to the basic native type. ```Rust use simple_endian::*;
let foo: u64be = 4.into(); let bar = foo.to_native();
println!("value: {:x}", bar); ```
And the type system ensures that native-endian values are never written without being converted into the proper endian.
Rust
let mut foo: u64be = 4.into();
foo = 7; // Will not compile without .into().
At its core, this crate centers around one trait, called SpecificEndian
The crate provides SpecificEndian for most of the built-in types in Rust, including:
At the time of this writing, the only type that doesn't have an implementation is char, and this is because some values of char that would be possible from the binary representation would cause a panic. Usually, you wouldn't want to store a char directly anyway, so this is probably a small limitation.
This crate also provides implementations of a variety of useful traits for the types that it wraps, including boolean logic implementations for the integer types. This allows some amount of logic to be performed directly without byte-swapping overhead.
```Rust use simple_endian::*;
let ip: BigEndian::
let network = ip & subnet_mask;
println!("value: {:x}", network); ```
As you see, the network is calculated by masking the IP address with the subnet mask in a way that the programmer barely has to think about the conversion operations.
Alternatively, you might want to define a structure with the elements typed so that it can be moved around as a unit.
```Rust use simple_endian::*;
struct NetworkConfig {
address: BigEndian
let config = NetworkConfig{address: 0x0a00000a.into(), mask: 0xff000000.into(), network: (0x0a00000a & 0xff000000).into()}
println!("value: {:x?}", config); ```
Note that the println! will interpret the values in native endian.
For the most part, the performance of the endian operations are extremely fast, even compared to native operations. The main exception is the std::fmt implementations, which are in some cases quite a bit slower than default. I'm open to suggestions on how to improve the performance, but it might be worth using .to_native() instead of directly printing the wrapped types in performance-critical contexts.
This crate allows for the manipulation of specific-endian structures in memory. It does not provide any facility for reading or writing those structures, which would probably be necessary in most use cases. See the following other crates for that functionality: