├── .gitignore ├── .vscode └── settings.json ├── Cargo.toml ├── LICENSE ├── README.md ├── benches └── benches.rs └── src ├── bitwise_ops.rs ├── comparison_ops.rs ├── formatting_ops.rs ├── lib.rs ├── math_ops.rs ├── neg_ops.rs ├── shift_ops.rs ├── shorthand_types.rs └── specific_endian.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | .test.bin 5 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "endianness", 4 | "impl", 5 | "impls", 6 | "mmap", 7 | "repr", 8 | "unergonomic" 9 | ] 10 | } -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "simple_endian" 3 | version = "0.3.2" 4 | authors = ["Erica Stith "] 5 | edition = "2021" 6 | license = "MIT" 7 | description = "A create for defining endianness within your data structures, to make handling portable data structures simpler." 8 | repository = "https://github.com/rexlunae/simple-endian-rs" 9 | keywords = ["endian", "byteorder", "big-endian", "little-endian", "data-structures"] 10 | documentation = "https://docs.rs/simple_endian/" 11 | homepage = "https://github.com/rexlunae/simple-endian-rs" 12 | readme = "README.md" 13 | 14 | [[bench]] 15 | name = "benches" 16 | harness = false 17 | 18 | [dev-dependencies] 19 | memmap = "0.7" 20 | bencher = "0.1.5" 21 | 22 | [features] 23 | default = ["bitwise", "comparisons", "format", "math_ops", 24 | "neg_ops", "shift_ops", "both_endian", "float_impls", "integer_impls", "byte_impls"] 25 | bitwise = ["integer_impls"] 26 | comparisons = [] 27 | format = [] 28 | math_ops= [] 29 | neg_ops = [] 30 | shift_ops = [] 31 | big_endian = [] 32 | little_endian = [] 33 | both_endian = ["big_endian", "little_endian"] 34 | float_impls = ["integer_impls"] 35 | integer_impls = [] 36 | byte_impls = [] 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Simple Endian Contributors 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # simple-endian 2 | 3 | As of the 0.3 release, this library works on stable Rust. 4 | 5 | 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. It should be fairly lightweight, and supports `#![no_std]`. 6 | 7 | The key difference between this crate and other crates for handling endian is that in this crate, you aren't doing conversions manually at all. You are just working in the endian that is appropriate to the data structures that you're dealing with, and we try to provide the needed traits and methods to do this in as normal a way as possible. 8 | 9 | ## Isn't there already a library for this 10 | 11 | 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: 12 | 13 | * The [endian](https://crates.io/crates/endian) crate. 14 | * The [byteorder](https://crates.io/crates/byteorder) crate. 15 | * The [bswap](https://crates.io/crates/bswap) crate. 16 | 17 | 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. 18 | 19 | ## So, why create another one 20 | 21 | 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. 22 | 23 | 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`. 24 | 25 | ## Goals of this project 26 | 27 | The goals of this crate are as follows: 28 | 29 | 1. Safely provide specific-endian types with low or no runtime overhead. There should be no runtime penalty when the host architecture matches the specified endianness, and very low penalty loads and stores otherwise. 30 | 2. Straightforward, architecture-independent declarative syntax which ensures that load and store operations as correct. 31 | 3. Ergonomic use patterns that maximize clarity and convenience without sacrificing correctness safety or correctness. 32 | 4. Incorrect handling of data should generate clear type errors at compile time. 33 | 5. Determination of correct endianness should be at declaration, and should not need to be repeated unless converting to a different endianness. 34 | 6. Support for all or Rust's built-in types where endianness is relevant. 35 | 7. The only dependency needed is the core crate. The std crate is used, however, for tests and benchmarks. 36 | 37 | ## Examples 38 | 39 | ```rust 40 | use simple_endian::*; 41 | 42 | let foo: u64be = 4.into(); //Stores 4 in foo in big endian. 43 | 44 | println!("raw: {:x}, value: {:x}", foo.to_bits(), foo); 45 | ``` 46 | 47 | 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 big endian representation interpreted as if little-endian, as it's stored in memory. Note that the ``.to_bits()` method is mostly there for debugging purposes, and should not be used often. 48 | 49 | This works in reverse as well: 50 | 51 | ```rust 52 | use simple_endian::*; 53 | 54 | let foo: u64be = 4.into(); 55 | let bar = u64::from(foo); 56 | 57 | println!("value: {:x}", bar); 58 | ``` 59 | 60 | If you prefer, there's a convenience method so that you don't need to explicitly convert back to the basic native type. 61 | 62 | ```rust 63 | use simple_endian::*; 64 | 65 | let foo: u64be = 4.into(); 66 | let bar = foo.to_native(); 67 | 68 | println!("value: {:x}", bar); 69 | ``` 70 | 71 | And the type system ensures that native-endian values are never written without being converted into the proper endian. 72 | 73 | ```rust 74 | let mut foo: u64be = 4.into(); 75 | foo = 7; // Will not compile without .into(). 76 | ``` 77 | 78 | ## How it works 79 | 80 | At its core, this crate centers around one trait, called `SpecificEndian`, and the generic structs `BigEndian` and `LittleEndian`. `SpecificEndian` is required to make `BigEndian` and `LittleEndian` structs. Any data type that implements `SpecificEndian`, even if it handles endianness in unusual ways, can be assigned `BigEndian` and `LittleEndian` variants using the structs in this crate, the main possibly limitation being that they need to use the same underlying structure. In fact, `u64be` is just a type alias for `BigEndian`. There is no memory footprint added by the `BigEndian` and `LittleEndian` structs, in fact, in most cases it uses the type T to store the data. The only purpose of the structs is to tag them for Rust's type system to enforce correct accesses. This means that it can be used directly within larger structs, and then the entire struct can be written to disk, send over a network socket, and otherwise shared between processor architectures using the same code regardless of host endian using declarative logic without any conditionals. 81 | 82 | This crate provides `SpecificEndian` implementations for most of the built-in types in Rust, including: 83 | 84 | * Single-byte values (`i8`, `u8`, `bool`), although this really doesn't do much but provide completeness. 85 | * The multi-byte integers: `u16`, `u32`, `u64`, `u128`, `usize`, `i16`, `i32`, `i64`, `i128`, `isize` 86 | * The floats: `f32`, `f64`. 87 | 88 | At the time of this writing, the only common built-in 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. 89 | 90 | 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, including bools. This allows most boolean logic operations to be performed without any endian conversions using ordinary operators. You are required to use same-endian operands, however, like this: 91 | 92 | ```rust 93 | use simple_endian::*; 94 | 95 | let ip: BigEndian:: = 0x0a00000a.into(); 96 | let subnet_mask = BigEndian::from(0xff000000u32); 97 | 98 | let network = ip & subnet_mask; 99 | 100 | println!("value: {:x}", network); 101 | ``` 102 | 103 | 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. 104 | 105 | Alternatively, you might want to define a structure with the elements typed so that it can be moved around as a unit. 106 | 107 | ```rust 108 | use simple_endian::*; 109 | 110 | #[derive(Debug)] 111 | #[repr(C)] 112 | struct NetworkConfig { 113 | address: BigEndian, 114 | mask: BigEndian, 115 | network: BigEndian, 116 | } 117 | 118 | let config = NetworkConfig{address: 0x0a00000a.into(), mask: 0xff000000.into(), network: (0x0a00000a & 0xff000000).into()} 119 | 120 | println!("value: {:x?}", config); 121 | ``` 122 | 123 | Note that the println! will convert the values to native endian. 124 | 125 | And finally, this crate implements a number of traits that allow most of the basic arithmetic operators to be used on the Big- and LittleEndian variants of all of the types, where appropriate, including for the floats. There is a certain amount of overhead to this, since each operation requires at least one and often two or more endian conversions, however, since this crate aims to minimize the cost of writing portable code, they are provided to reduce friction to adoption. If you are writing code that is extremely sensitive to such overhead, it might make sense to convert to native endian, do your operations, and then store back in the specified endian using `.into()` or similar. That said, the overhead is often very small, and Rust's optimizer is very good, so I would encourage you to do some actual benchmarking before taking an unergonomic approach to your code. There are too many traits implemented to list them here, so I recommend consulting [the documentation](https://docs.rs/simple_endian/). Alternatively, you could just try what you want to do, and see if it compiles. It shouldn't ever allow you to compile something that doesn't handle endianness correctly unless you work pretty hard at it. 126 | 127 | ### Representations and ABI 128 | 129 | You might notice that we used `#[repr(C)]` in the data struct above, and you might be wondering why. It is often the case that you want to write a `struct` that has a very specific layout when you are writing structures that will be directly read from and written to some medium. Rust's default ABI does not guarantee this. For that reason, all of the structs defined in this crate are `#[repr(transparent)]`, and it is strongly recommended if you do plan to directly write these structures to disk or the network, that you do something to ensure a consistent layout similar or otherwise guarantee the order in which the fields are stored. 130 | 131 | ## Operations on Types 132 | 133 | In addition to offering support for ensuring that correct endianness is used by leveraging the Rust type system, this crate also provides implementations of a number of traits from the `core` library that allow you to work with values directly without converting them to native endian types first. In many cases, this is literally a zero-cost capability, because bitwise operations are endian-agnostic, and as long as you are using other `SpecificEndian` types, there is no overhead to doing operations on them directly. In cases where a conversion to native endian is necessary, the crate will perform the conversion, and return a value in the same type as the input. 134 | 135 | ## Features 136 | 137 | Although this crate includes a lot of useful functionality up front, including it all can increase your compiled size significantly. For size-conscious applications, I recommend not including everything. 138 | 139 | By default, this crate will compile with all supported features. Although this means that in most cases, almost anything you would want to do would work out of the box, in practical terms, this can make the crate rather large. To avoid bloat, it might be best to set `default-features = false` in your "Cargo.toml", and add back in the features you actually need. 140 | 141 | The two most useful features are probably the ones that control support for big- and little- endians: 142 | 143 | * `big_endian` 144 | * `little_endian` 145 | 146 | Others are broken into categories: 147 | 148 | * Operations types - These can make the use of `SpecificEndian` types more ergonimic, and allow for some amount of optimization by avoiding unnecessary convertions to and from native endian. 149 | * `bitwise` 150 | * `comparisons` 151 | * `math_ops` 152 | * `neg_ops` 153 | * `shift_ops` 154 | * Support for formatting in the `format` feature. 155 | * Support for different types 156 | * `float_impls` 157 | * `integer_impls` 158 | * `byte_impls` 159 | 160 | ## Performance 161 | 162 | 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. 163 | 164 | ## See Also 165 | 166 | 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: 167 | 168 | * Rust's standard std::mem::[transmute](https://doc.rust-lang.org/std/mem/fn.transmute.html) call: 169 | * [safe-transmute](https://crates.io/crates/safe-transmute) 170 | * [endian-trait](https://crates.io/crates/endian_trait) 171 | * [byteordered](https://crates.io/crates/byteordered) 172 | * [persistance](https://crates.io/crates/persistence) - A library that wraps structs in mmap, and can be used well with this to make those structs portable. 173 | * [endian-type](https://crates.io/crates/endian-type) - The endian-type library. This appears to be essentially the same approach as this crate, but contains less functionality. 174 | * [endian-types](https://crates.io/crates/endian-types) - Another very similar approach. 175 | -------------------------------------------------------------------------------- /benches/benches.rs: -------------------------------------------------------------------------------- 1 | //use criterion::{criterion_group, criterion_main, Criterion}; 2 | use bencher::{benchmark_group, benchmark_main, Bencher}; 3 | 4 | benchmark_group!( 5 | benches, 6 | bench_integer_be, 7 | bench_integer_le, 8 | bench_integer_ne, 9 | bench_fp_be, 10 | bench_fp_le, 11 | bench_fp_ne, 12 | base_endian_test_be, 13 | base_endian_test_le, 14 | base_endian_test_ne, 15 | base_endian_test_structured 16 | ); 17 | //benchmark_group!(benches, bench_integer_be); 18 | benchmark_main!(benches); 19 | 20 | //criterion_group!(benches, bench_integer_be); 21 | //criterion_main!(benches); 22 | 23 | use simple_endian::{BigEndian, LittleEndian}; 24 | 25 | fn bench_integer_be(b: &mut Bencher) { 26 | b.iter(|| { 27 | let mut a = BigEndian::from(1234567890); 28 | for _ in 0..10 { 29 | a += BigEndian::from(101010); 30 | a &= BigEndian::from(0xf0f0f0); 31 | a *= BigEndian::from(123); 32 | a /= BigEndian::from(543); 33 | } 34 | println!("{}", a); 35 | }); 36 | } 37 | fn bench_integer_le(b: &mut Bencher) { 38 | b.iter(|| { 39 | let mut a = LittleEndian::from(1234567890); 40 | for _ in 0..10 { 41 | a += LittleEndian::from(101010); 42 | a &= LittleEndian::from(0xf0f0f0); 43 | a *= LittleEndian::from(123); 44 | a /= LittleEndian::from(543); 45 | } 46 | println!("{}", a); 47 | }); 48 | } 49 | fn bench_integer_ne(b: &mut Bencher) { 50 | b.iter(|| { 51 | let mut a = 1234567890; 52 | for _ in 0..10 { 53 | a += 101010; 54 | a &= 0xf0f0f0; 55 | a *= 123; 56 | a /= 543; 57 | } 58 | println!("{}", a); 59 | }); 60 | } 61 | 62 | fn bench_fp_be(b: &mut Bencher) { 63 | b.iter(|| { 64 | let mut a = BigEndian::from(1234567890.1); 65 | for _ in 0..10 { 66 | a += BigEndian::from(101010.0); 67 | a *= BigEndian::from(123.0); 68 | a /= BigEndian::from(543.0); 69 | } 70 | println!("{}", a); 71 | }); 72 | } 73 | 74 | fn bench_fp_le(b: &mut Bencher) { 75 | b.iter(|| { 76 | let mut a = LittleEndian::from(1234567890.1); 77 | for _ in 0..10 { 78 | a += LittleEndian::from(101010.0); 79 | a *= LittleEndian::from(123.0); 80 | a /= LittleEndian::from(543.0); 81 | } 82 | println!("{}", a); 83 | }); 84 | } 85 | 86 | fn bench_fp_ne(b: &mut Bencher) { 87 | b.iter(|| { 88 | let mut a = 1234567890.1; 89 | for _ in 0..10 { 90 | a += 101010.0; 91 | a *= 123.0; 92 | a /= 543.0; 93 | } 94 | println!("{}", a); 95 | }); 96 | } 97 | 98 | fn base_endian_test_be(b: &mut Bencher) { 99 | b.iter(|| { 100 | for _ in 0..1000 { 101 | let a = i32::from_be(0xa5a5a5); 102 | println!("{}", a); 103 | } 104 | }); 105 | } 106 | 107 | fn base_endian_test_le(b: &mut Bencher) { 108 | b.iter(|| { 109 | for _ in 0..1000 { 110 | let a = i32::from_le(0xa5a5a5); 111 | println!("{}", a); 112 | } 113 | }); 114 | } 115 | 116 | fn base_endian_test_ne(b: &mut Bencher) { 117 | b.iter(|| { 118 | for _ in 0..1000 { 119 | let a = 0xa5a5a5_i32; 120 | println!("{}", a); 121 | } 122 | }); 123 | } 124 | 125 | fn base_endian_test_structured(b: &mut Bencher) { 126 | b.iter(|| { 127 | for _ in 0..1000 { 128 | let a = LittleEndian::from(0xa5a5a5_i32); 129 | println!("{}", a); 130 | } 131 | }); 132 | } 133 | -------------------------------------------------------------------------------- /src/bitwise_ops.rs: -------------------------------------------------------------------------------- 1 | //! Bitwise operations. These should be equally fast in either endian. 2 | //! 3 | //! ```rust 4 | //! // These should all be basically zero-cost: 5 | //! use simple_endian::*; 6 | //! let mut a = BigEndian::from(0xf8dc); 7 | //! let mask = BigEndian::from(0xf0f0f); 8 | //! a &= mask; 9 | //! a |= BigEndian::from(0xfff0000) | mask; 10 | //! a ^= 0x5555555.into(); 11 | //! ``` 12 | 13 | use core::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Not}; 14 | 15 | use super::*; 16 | 17 | /// Implement the bitwise operations on the types. These should be as fast in either endian, because they are endian-agnostic. 18 | #[allow(unused_macros)] 19 | macro_rules! add_bitwise_ops { 20 | ($wrap_ty:ty) => { 21 | impl BitAnd for $wrap_ty { 22 | type Output = Self; 23 | fn bitand(self, rhs: Self) -> Self::Output { 24 | Self(self.0 & rhs.0) 25 | } 26 | } 27 | impl BitAndAssign for $wrap_ty { 28 | fn bitand_assign(&mut self, rhs: Self) { 29 | *self = *self & rhs 30 | } 31 | } 32 | impl BitXor for $wrap_ty { 33 | // We don't need to convert endian for this op. 34 | type Output = Self; 35 | 36 | fn bitxor(self, rhs: Self) -> Self::Output { 37 | Self(self.0 ^ rhs.0) 38 | } 39 | } 40 | impl BitXorAssign for $wrap_ty { 41 | fn bitxor_assign(&mut self, rhs: Self) { 42 | *self = *self ^ rhs 43 | } 44 | } 45 | impl BitOr for $wrap_ty { 46 | type Output = Self; 47 | 48 | fn bitor(self, rhs: Self) -> Self { 49 | Self(self.0 | rhs.0) 50 | } 51 | } 52 | impl BitOrAssign for $wrap_ty { 53 | fn bitor_assign(&mut self, rhs: Self) { 54 | *self = *self | rhs; 55 | } 56 | } 57 | impl Not for $wrap_ty { 58 | type Output = Self; 59 | 60 | fn not(self) -> Self::Output { 61 | Self(!self.0) 62 | } 63 | } 64 | }; 65 | } 66 | 67 | #[cfg(feature = "byte_impls")] 68 | mod bitwise_byte_ops { 69 | use super::*; 70 | #[cfg(feature = "big_endian")] 71 | mod be { 72 | use super::*; 73 | add_bitwise_ops!(BigEndian); 74 | add_bitwise_ops!(BigEndian); 75 | add_bitwise_ops!(BigEndian); 76 | } 77 | #[cfg(feature = "little_endian")] 78 | mod le { 79 | use super::*; 80 | add_bitwise_ops!(LittleEndian); 81 | add_bitwise_ops!(LittleEndian); 82 | add_bitwise_ops!(LittleEndian); 83 | } 84 | } 85 | 86 | #[cfg(feature = "integer_impls")] 87 | mod bitwise_integer_ops { 88 | use super::*; 89 | #[cfg(feature = "big_endian")] 90 | mod be { 91 | use super::*; 92 | add_bitwise_ops!(BigEndian); 93 | add_bitwise_ops!(BigEndian); 94 | add_bitwise_ops!(BigEndian); 95 | add_bitwise_ops!(BigEndian); 96 | add_bitwise_ops!(BigEndian); 97 | add_bitwise_ops!(BigEndian); 98 | add_bitwise_ops!(BigEndian); 99 | add_bitwise_ops!(BigEndian); 100 | add_bitwise_ops!(BigEndian); 101 | add_bitwise_ops!(BigEndian); 102 | } 103 | 104 | #[cfg(feature = "little_endian")] 105 | mod le { 106 | use super::*; 107 | add_bitwise_ops!(LittleEndian); 108 | add_bitwise_ops!(LittleEndian); 109 | add_bitwise_ops!(LittleEndian); 110 | add_bitwise_ops!(LittleEndian); 111 | add_bitwise_ops!(LittleEndian); 112 | add_bitwise_ops!(LittleEndian); 113 | add_bitwise_ops!(LittleEndian); 114 | add_bitwise_ops!(LittleEndian); 115 | add_bitwise_ops!(LittleEndian); 116 | add_bitwise_ops!(LittleEndian); 117 | } 118 | } 119 | 120 | #[cfg(test)] 121 | mod tests { 122 | use crate::*; 123 | 124 | #[test] 125 | fn bit_and_test() { 126 | let be1 = LittleEndian::::from(0x0f0); 127 | let be2 = LittleEndian::::from(0xff0); 128 | assert_eq!(0x0f0, u64::from(be1 & be2)); 129 | } 130 | 131 | #[test] 132 | fn unary_not_test() { 133 | let be1 = BigEndian::::from(0x0f0); 134 | assert_eq!(0xff0f, u16::from(!be1)); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/comparison_ops.rs: -------------------------------------------------------------------------------- 1 | //! Comparison ops. 2 | #[allow(unused_imports)] 3 | use core::cmp::Ordering; 4 | 5 | #[allow(unused_imports)] 6 | use super::*; 7 | 8 | /// For types that have PartialEq. 9 | #[allow(unused_macros)] 10 | macro_rules! add_equality_ops { 11 | ($wrap_ty:ty) => { 12 | impl PartialOrd for $wrap_ty { 13 | fn partial_cmp(&self, other: &Self) -> Option { 14 | self.to_native().partial_cmp(&other.to_native()) 15 | } 16 | } 17 | }; 18 | } 19 | 20 | // The floats can only have PartialOrd, not Ord, because they only have PartialEq and not Eq. 21 | #[cfg(feature = "float_impls")] 22 | mod float_comps { 23 | use super::*; 24 | #[cfg(feature = "big_endian")] 25 | mod be { 26 | use super::*; 27 | add_equality_ops!(BigEndian); 28 | add_equality_ops!(BigEndian); 29 | } 30 | #[cfg(feature = "little_endian")] 31 | mod le { 32 | use super::*; 33 | add_equality_ops!(LittleEndian); 34 | add_equality_ops!(LittleEndian); 35 | } 36 | } 37 | 38 | /// For types that implement Eq. 39 | #[allow(unused_macros)] 40 | macro_rules! add_full_equality_ops { 41 | ($wrap_ty:ty) => { 42 | impl Ord for $wrap_ty { 43 | fn cmp(&self, other: &Self) -> Ordering { 44 | self.to_native().cmp(&other.to_native()) 45 | } 46 | } 47 | add_equality_ops!($wrap_ty); 48 | }; 49 | } 50 | 51 | // We have to separate Ord because f32/64 don't have Eq trait. 52 | #[cfg(feature = "integer_impls")] 53 | mod integer_comps { 54 | use super::*; 55 | #[cfg(feature = "big_endian")] 56 | mod be { 57 | use super::*; 58 | add_full_equality_ops!(BigEndian); 59 | add_full_equality_ops!(BigEndian); 60 | add_full_equality_ops!(BigEndian); 61 | add_full_equality_ops!(BigEndian); 62 | add_full_equality_ops!(BigEndian); 63 | add_full_equality_ops!(BigEndian); 64 | add_full_equality_ops!(BigEndian); 65 | add_full_equality_ops!(BigEndian); 66 | add_full_equality_ops!(BigEndian); 67 | add_full_equality_ops!(BigEndian); 68 | } 69 | #[cfg(feature = "little_endian")] 70 | mod le { 71 | use super::*; 72 | add_full_equality_ops!(LittleEndian); 73 | add_full_equality_ops!(LittleEndian); 74 | add_full_equality_ops!(LittleEndian); 75 | add_full_equality_ops!(LittleEndian); 76 | add_full_equality_ops!(LittleEndian); 77 | add_full_equality_ops!(LittleEndian); 78 | add_full_equality_ops!(LittleEndian); 79 | add_full_equality_ops!(LittleEndian); 80 | add_full_equality_ops!(LittleEndian); 81 | add_full_equality_ops!(LittleEndian); 82 | } 83 | } 84 | 85 | #[cfg(feature = "byte_impls")] 86 | mod byte_comps { 87 | use super::*; 88 | #[cfg(feature = "big_endian")] 89 | mod be { 90 | use super::*; 91 | add_full_equality_ops!(BigEndian); 92 | add_full_equality_ops!(BigEndian); 93 | add_full_equality_ops!(BigEndian); 94 | } 95 | #[cfg(feature = "little_endian")] 96 | mod le { 97 | use super::*; 98 | add_full_equality_ops!(LittleEndian); 99 | add_full_equality_ops!(LittleEndian); 100 | add_full_equality_ops!(LittleEndian); 101 | } 102 | } 103 | 104 | #[cfg(test)] 105 | mod tests { 106 | use crate::*; 107 | 108 | #[test] 109 | fn equality_test() { 110 | let be1 = BigEndian::from(12345); 111 | let be2 = BigEndian::from(12345); 112 | assert!(be1 == be2); 113 | } 114 | 115 | #[test] 116 | fn not_equality_test() { 117 | let be1 = BigEndian::from(12345); 118 | let be2 = BigEndian::from(34565); 119 | assert!(be1 != be2); 120 | } 121 | 122 | #[test] 123 | fn lt_test() { 124 | let be1 = BigEndian::from(12345); 125 | let be2 = BigEndian::from(34565); 126 | assert!(be1 < be2); 127 | } 128 | 129 | #[test] 130 | fn gt_test() { 131 | let be1 = BigEndian::from(34565); 132 | let be2 = BigEndian::from(12345); 133 | assert!(be1 > be2); 134 | } 135 | 136 | #[test] 137 | fn lt_fp_be() { 138 | let be1 = BigEndian::from(1234.5678); 139 | let be2 = BigEndian::from(6234.5678); 140 | assert!(be1 < be2); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/formatting_ops.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | Implementations for formatting the various types. 3 | */ 4 | use core::fmt::{Binary, Display, Formatter, LowerHex, Octal, Result, UpperHex}; 5 | 6 | use super::*; 7 | 8 | impl> UpperHex for BigEndian { 9 | fn fmt(&self, f: &mut Formatter<'_>) -> Result { 10 | write!(f, "{:X}", self.to_native()) // delegate to i32's implementation 11 | } 12 | } 13 | impl> UpperHex for LittleEndian { 14 | fn fmt(&self, f: &mut Formatter<'_>) -> Result { 15 | write!(f, "{:X}", self.to_native()) // delegate to i32's implementation 16 | } 17 | } 18 | 19 | impl> LowerHex for BigEndian { 20 | fn fmt(&self, f: &mut Formatter<'_>) -> Result { 21 | write!(f, "{:x}", self.to_native()) // delegate to i32's implementation 22 | } 23 | } 24 | 25 | impl> LowerHex for LittleEndian { 26 | fn fmt(&self, f: &mut Formatter<'_>) -> Result { 27 | write!(f, "{:x}", self.to_native()) // delegate to i32's implementation 28 | } 29 | } 30 | 31 | impl> Octal for BigEndian { 32 | fn fmt(&self, f: &mut Formatter<'_>) -> Result { 33 | write!(f, "{:o}", self.to_native()) // delegate to i32's implementation 34 | } 35 | } 36 | 37 | impl> Octal for LittleEndian { 38 | fn fmt(&self, f: &mut Formatter<'_>) -> Result { 39 | write!(f, "{:o}", self.to_native()) // delegate to i32's implementation 40 | } 41 | } 42 | 43 | impl> Binary for BigEndian { 44 | fn fmt(&self, f: &mut Formatter<'_>) -> Result { 45 | write!(f, "{:b}", self.to_native()) // delegate to i32's implementation 46 | } 47 | } 48 | 49 | impl> Binary for LittleEndian { 50 | fn fmt(&self, f: &mut Formatter<'_>) -> Result { 51 | write!(f, "{:b}", self.to_native()) // delegate to i32's implementation 52 | } 53 | } 54 | 55 | impl> Display for BigEndian { 56 | fn fmt(&self, f: &mut Formatter<'_>) -> Result { 57 | write!(f, "{}", self.to_native()) // delegate to i32's implementation 58 | } 59 | } 60 | 61 | impl> Display for LittleEndian { 62 | fn fmt(&self, f: &mut Formatter<'_>) -> Result { 63 | write!(f, "{}", self.to_native()) // delegate to i32's implementation 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(test), no_std)] 2 | /*! 3 | Many byte-order-handling libraries focus on providing code to convert to and from big- or little-endian. However, 4 | this requires users of those libraries to use a lot of explicit logic. This library uses the Rust type system to 5 | enforce conversions invisibly, and also ensure that they are done consistently. A struct member can be read and written 6 | simply using the standard From and Into trait methods (from() and into()). No explicit endian checks are required. 7 | 8 | # Example 1: 9 | 10 | ```rust 11 | use simple_endian::*; 12 | 13 | fn init() { 14 | #[repr(C)] 15 | struct BinPacket { 16 | a: u64be, 17 | b: u32be, 18 | } 19 | let mut bp = BinPacket{a: 0xfe.into(), b: 10.into()}; 20 | let new_a = bp.a.to_native() * 1234; 21 | 22 | bp.a = new_a.into(); 23 | bp.b = 1234.into(); 24 | } 25 | ``` 26 | 27 | Trying to write `bp.a = new_a;` causes an error because the type u64 can't be directly stored. 28 | 29 | # Example 2: Writing a portable struct to a file. 30 | 31 | Of course, just storing things in memory isn't that useful unless you write somewhere. 32 | 33 | ```rust 34 | use simple_endian::*; 35 | use std::fs::File; 36 | use std::io::prelude::*; 37 | use std::mem::{transmute, size_of}; 38 | 39 | // We have to specify a representation in order to define the layout. 40 | #[repr(C)] 41 | struct BinBEStruct { 42 | pub a: u64be, 43 | b: u64be, 44 | c: f64be, 45 | } 46 | 47 | fn main() -> std::io::Result<()> { 48 | let bin_struct = BinBEStruct{a: 345.into(), b: 0xfee.into(), c: 9.345.into()}; 49 | 50 | let mut pos = 0; 51 | let mut data_file = File::create(".test.bin")?; 52 | let buffer = unsafe { transmute::<&BinBEStruct, &[u8; size_of::()]>(&bin_struct) }; 53 | 54 | while pos < buffer.len() { 55 | let bytes_written = data_file.write(&buffer[pos..])?; 56 | pos += bytes_written; 57 | } 58 | Ok(()) 59 | } 60 | ``` 61 | # Example 3: Mmapping a portable struct with the memmap crate. 62 | 63 | You'll need to add memmap to your Cargo.toml to get this to actually work: 64 | 65 | ```rust 66 | extern crate memmap; 67 | 68 | use std::{ 69 | io::Error, 70 | fs::OpenOptions, 71 | mem::size_of, 72 | }; 73 | 74 | use memmap::MmapOptions; 75 | use simple_endian::*; 76 | 77 | #[repr(C)] 78 | struct MyBEStruct { 79 | header: u64be, 80 | label: [u8; 8], 81 | count: u128be, 82 | } 83 | 84 | fn main() -> Result<(), Error> { 85 | let file = OpenOptions::new() 86 | .read(true).write(true).create(true) 87 | .open(".test.bin")?; 88 | 89 | // Truncate the file to the size of the header. 90 | file.set_len(size_of::() as u64)?; 91 | let mut mmap = unsafe { MmapOptions::new().map_mut(&file)? }; 92 | 93 | let mut ptr = mmap.as_mut_ptr() as *mut MyBEStruct; 94 | 95 | unsafe { 96 | // Set the magic number 97 | (*ptr).header = 0xfeedface.into(); 98 | 99 | // Increment the counter each time we run. 100 | (*ptr).count += 1.into(); 101 | 102 | (*ptr).label = *b"Iamhere!"; 103 | } 104 | 105 | println!("done."); 106 | Ok(()) 107 | } 108 | ``` 109 | 110 | */ 111 | #[warn(soft_unstable)] 112 | 113 | /// The main part of the library. Contains the trait SpecificEndian and BigEndian and LittleEndian structs, as well as the 114 | /// implementation of those on the primitive types. 115 | mod specific_endian; 116 | pub use specific_endian::*; 117 | 118 | /// Bitwise operations. These should be equally fast in any endian. 119 | #[cfg(feature = "bitwise")] 120 | mod bitwise_ops; 121 | 122 | /// Ops for comparisons and ordering. 123 | #[cfg(feature = "comparisons")] 124 | mod comparison_ops; 125 | 126 | /// Shift operations. 127 | #[cfg(feature = "shift_ops")] 128 | mod shift_ops; 129 | 130 | /// General math operations. 131 | #[cfg(feature = "math_ops")] 132 | mod math_ops; 133 | 134 | /// Negations. 135 | #[cfg(feature = "neg_ops")] 136 | mod neg_ops; 137 | 138 | /// Formatter impls. 139 | #[cfg(feature = "format")] 140 | mod formatting_ops; 141 | 142 | /// The shorthand types (e.g u64be, f32le, etc) 143 | mod shorthand_types; 144 | pub use shorthand_types::*; 145 | -------------------------------------------------------------------------------- /src/math_ops.rs: -------------------------------------------------------------------------------- 1 | //! The math operations. These all have some cost because they require conversion to native endian. 2 | #[allow(unused_imports)] 3 | use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign}; 4 | 5 | #[allow(unused_imports)] 6 | use super::*; 7 | 8 | #[allow(unused_macros)] 9 | macro_rules! add_math_ops { 10 | ($wrap_ty:ty) => { 11 | impl Add for $wrap_ty { 12 | type Output = Self; 13 | 14 | fn add(self, other: Self) -> Self { 15 | Self::from(self.to_native() + other.to_native()) 16 | } 17 | } 18 | 19 | impl AddAssign for $wrap_ty { 20 | fn add_assign(&mut self, other: Self) { 21 | *self = *self + other; 22 | } 23 | } 24 | 25 | impl Mul for $wrap_ty { 26 | type Output = Self; 27 | 28 | fn mul(self, other: Self) -> Self { 29 | Self::from(self.to_native() * other.to_native()) 30 | } 31 | } 32 | 33 | impl MulAssign for $wrap_ty { 34 | fn mul_assign(&mut self, other: Self) { 35 | *self = *self * other; 36 | } 37 | } 38 | 39 | impl Div for $wrap_ty { 40 | type Output = Self; 41 | 42 | fn div(self, other: Self) -> Self { 43 | Self::from(self.to_native() / other.to_native()) 44 | } 45 | } 46 | 47 | impl DivAssign for $wrap_ty { 48 | fn div_assign(&mut self, other: Self) { 49 | *self = *self / other; 50 | } 51 | } 52 | 53 | impl Sub for $wrap_ty { 54 | type Output = Self; 55 | 56 | fn sub(self, other: Self) -> Self { 57 | Self::from(self.to_native() - other.to_native()) 58 | } 59 | } 60 | impl SubAssign for $wrap_ty { 61 | fn sub_assign(&mut self, other: Self) { 62 | *self = *self - other; 63 | } 64 | } 65 | }; 66 | } 67 | 68 | #[cfg(feature = "big_endian")] 69 | mod be { 70 | use super::*; 71 | #[cfg(feature = "byte_impls")] 72 | mod bytes { 73 | use super::*; 74 | add_math_ops!(BigEndian); 75 | add_math_ops!(BigEndian); 76 | } 77 | 78 | #[cfg(feature = "integer_impls")] 79 | mod integers { 80 | use super::*; 81 | add_math_ops!(BigEndian); 82 | add_math_ops!(BigEndian); 83 | add_math_ops!(BigEndian); 84 | add_math_ops!(BigEndian); 85 | add_math_ops!(BigEndian); 86 | add_math_ops!(BigEndian); 87 | add_math_ops!(BigEndian); 88 | add_math_ops!(BigEndian); 89 | add_math_ops!(BigEndian); 90 | add_math_ops!(BigEndian); 91 | } 92 | 93 | #[cfg(feature = "float_impls")] 94 | mod floats { 95 | use super::*; 96 | add_math_ops!(BigEndian); 97 | add_math_ops!(BigEndian); 98 | } 99 | } 100 | 101 | #[cfg(feature = "little_endian")] 102 | mod le { 103 | use super::*; 104 | #[cfg(feature = "byte_impls")] 105 | mod bytes { 106 | use super::*; 107 | add_math_ops!(LittleEndian); 108 | add_math_ops!(LittleEndian); 109 | } 110 | 111 | #[cfg(feature = "integer_impls")] 112 | mod integers { 113 | use super::*; 114 | add_math_ops!(LittleEndian); 115 | add_math_ops!(LittleEndian); 116 | add_math_ops!(LittleEndian); 117 | add_math_ops!(LittleEndian); 118 | add_math_ops!(LittleEndian); 119 | add_math_ops!(LittleEndian); 120 | add_math_ops!(LittleEndian); 121 | add_math_ops!(LittleEndian); 122 | add_math_ops!(LittleEndian); 123 | add_math_ops!(LittleEndian); 124 | } 125 | 126 | #[cfg(feature = "float_impls")] 127 | mod floats { 128 | use super::*; 129 | add_math_ops!(LittleEndian); 130 | add_math_ops!(LittleEndian); 131 | } 132 | } 133 | 134 | #[cfg(test)] 135 | mod tests { 136 | use crate::*; 137 | 138 | #[test] 139 | fn add_fp_be() { 140 | let mut be1 = f64be::from(1234.5678); 141 | be1 += 1.0.into(); 142 | be1 += 1.0.into(); 143 | assert_eq!(be1, 1236.5678.into()); 144 | } 145 | 146 | #[test] 147 | fn subtract_fp_be() { 148 | let mut be1 = f64be::from(1234.5678); 149 | be1 -= 1.0.into(); 150 | be1 -= 1.0.into(); 151 | assert_eq!(be1, 1232.5678.into()); 152 | } 153 | 154 | #[test] 155 | fn mul_fp_be() { 156 | let mut be1 = f64be::from(1234.5678); 157 | be1 *= 10.0.into(); 158 | be1 *= 10.0.into(); 159 | assert_eq!(be1, 123456.78.into()); 160 | } 161 | 162 | #[test] 163 | fn div_fp_be() { 164 | let mut ne1: f64 = 1234.5678; 165 | let mut be1 = f64be::from(ne1); 166 | be1 /= 10.0.into(); 167 | ne1 /= 10.0; 168 | be1 /= 10.0.into(); 169 | ne1 /= 10.0; 170 | assert_eq!(ne1, be1.into()); 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/neg_ops.rs: -------------------------------------------------------------------------------- 1 | //! Module adding negation to the types where it's possible. 2 | use super::*; 3 | use core::ops::Neg; 4 | 5 | macro_rules! add_neg_ops { 6 | ($wrap_ty:ty) => { 7 | impl Neg for $wrap_ty { 8 | type Output = Self; 9 | 10 | fn neg(self) -> Self { 11 | Self::from(-self.to_native()) 12 | } 13 | } 14 | }; 15 | } 16 | 17 | #[cfg(feature = "big_endian")] 18 | mod be { 19 | use super::*; 20 | #[cfg(feature = "byte_impls")] 21 | add_neg_ops!(BigEndian); 22 | 23 | #[cfg(feature = "integer_impls")] 24 | mod integers { 25 | use super::*; 26 | add_neg_ops!(BigEndian); 27 | add_neg_ops!(BigEndian); 28 | add_neg_ops!(BigEndian); 29 | add_neg_ops!(BigEndian); 30 | add_neg_ops!(BigEndian); 31 | } 32 | 33 | #[cfg(feature = "float_impls")] 34 | mod floats { 35 | use super::*; 36 | add_neg_ops!(BigEndian); 37 | add_neg_ops!(BigEndian); 38 | } 39 | } 40 | 41 | #[cfg(feature = "little_endian")] 42 | mod le { 43 | use super::*; 44 | #[cfg(feature = "byte_impls")] 45 | add_neg_ops!(LittleEndian); 46 | 47 | #[cfg(feature = "integer_impls")] 48 | mod integers { 49 | use super::*; 50 | add_neg_ops!(LittleEndian); 51 | add_neg_ops!(LittleEndian); 52 | add_neg_ops!(LittleEndian); 53 | add_neg_ops!(LittleEndian); 54 | add_neg_ops!(LittleEndian); 55 | } 56 | 57 | #[cfg(feature = "float_impls")] 58 | mod floats { 59 | use super::*; 60 | add_neg_ops!(LittleEndian); 61 | add_neg_ops!(LittleEndian); 62 | } 63 | } 64 | 65 | #[cfg(test)] 66 | mod tests { 67 | use crate::*; 68 | #[test] 69 | fn negate() { 70 | let be1 = BigEndian::from(1); 71 | let be2 = -be1; 72 | println!("{}, {}", be1, be2); 73 | assert_eq!(be2, i32be::from(-1)); 74 | } 75 | #[test] 76 | fn negate_fp() { 77 | let be1 = BigEndian::from(1.0); 78 | let be2 = -be1; 79 | println!("{}, {}", be1, be2); 80 | assert_eq!(be2, f64be::from(-1.0)); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/shift_ops.rs: -------------------------------------------------------------------------------- 1 | //! Bitshift operations, for integer types only. 2 | 3 | #[allow(unused_imports)] 4 | use core::ops::{Shl, ShlAssign, Shr, ShrAssign}; 5 | 6 | #[allow(unused_imports)] 7 | use super::*; 8 | 9 | #[allow(unused_macros)] 10 | macro_rules! add_shift_ops { 11 | ($wrap_ty:ty) => { 12 | impl Shl for $wrap_ty { 13 | type Output = Self; 14 | 15 | fn shl(self, other: Self) -> Self { 16 | Self::from(self.to_native() << other.to_native()) 17 | } 18 | } 19 | impl ShlAssign for $wrap_ty { 20 | fn shl_assign(&mut self, rhs: Self) { 21 | *self = Self::from((*self).to_native() << rhs.to_native()); 22 | } 23 | } 24 | impl Shr for $wrap_ty { 25 | type Output = Self; 26 | 27 | fn shr(self, other: Self) -> Self { 28 | Self::from(self.to_native() >> other.to_native()) 29 | } 30 | } 31 | impl ShrAssign for $wrap_ty { 32 | fn shr_assign(&mut self, rhs: Self) { 33 | *self = Self::from((*self).to_native() >> rhs.to_native()); 34 | } 35 | } 36 | }; 37 | } 38 | 39 | #[cfg(feature = "big_endian")] 40 | mod be { 41 | use super::*; 42 | #[cfg(feature = "byte_impls")] 43 | mod bytes { 44 | use super::*; 45 | add_shift_ops!(BigEndian); 46 | add_shift_ops!(BigEndian); 47 | } 48 | 49 | #[cfg(feature = "integer_impls")] 50 | mod integers { 51 | use super::*; 52 | add_shift_ops!(BigEndian); 53 | add_shift_ops!(BigEndian); 54 | add_shift_ops!(BigEndian); 55 | add_shift_ops!(BigEndian); 56 | add_shift_ops!(BigEndian); 57 | add_shift_ops!(BigEndian); 58 | add_shift_ops!(BigEndian); 59 | add_shift_ops!(BigEndian); 60 | add_shift_ops!(BigEndian); 61 | add_shift_ops!(BigEndian); 62 | } 63 | } 64 | 65 | #[cfg(feature = "big_endian")] 66 | mod le { 67 | use super::*; 68 | #[cfg(feature = "byte_impls")] 69 | mod bytes { 70 | use super::*; 71 | add_shift_ops!(LittleEndian); 72 | add_shift_ops!(LittleEndian); 73 | } 74 | 75 | #[cfg(feature = "integer_impls")] 76 | mod integers { 77 | use super::*; 78 | add_shift_ops!(LittleEndian); 79 | add_shift_ops!(LittleEndian); 80 | add_shift_ops!(LittleEndian); 81 | add_shift_ops!(LittleEndian); 82 | add_shift_ops!(LittleEndian); 83 | add_shift_ops!(LittleEndian); 84 | add_shift_ops!(LittleEndian); 85 | add_shift_ops!(LittleEndian); 86 | add_shift_ops!(LittleEndian); 87 | add_shift_ops!(LittleEndian); 88 | } 89 | } 90 | 91 | #[cfg(test)] 92 | mod tests { 93 | use crate::*; 94 | 95 | #[test] 96 | fn shl_be() { 97 | let mut ne1 = 0xfee1; 98 | let mut be1 = u64be::from(ne1); 99 | be1 <<= 5.into(); 100 | ne1 <<= 5; 101 | be1 <<= 5.into(); 102 | ne1 <<= 5; 103 | assert_eq!(ne1, be1.into()); 104 | } 105 | 106 | #[test] 107 | fn shr_be() { 108 | let mut ne1 = 0xfee1; 109 | let mut be1 = u64be::from(ne1); 110 | be1 >>= 5.into(); 111 | ne1 >>= 5; 112 | be1 >>= 5.into(); 113 | ne1 >>= 5; 114 | assert_eq!(ne1, be1.into()); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/shorthand_types.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | Provides a bunch of short type names for easier declaration. All follow a pattern of LittleEndian = BASETYPEle and BigEndian = BASETYPEbe 3 | */ 4 | 5 | #![allow(non_camel_case_types)] 6 | use super::*; 7 | /// Shorthand for `LittleEndian` 8 | pub type u16le = LittleEndian; 9 | /// Shorthand for `BigEndian` 10 | pub type u16be = BigEndian; 11 | /// Shorthand for `LittleEndian` 12 | pub type u32le = LittleEndian; 13 | /// Shorthand for `BigEndian` 14 | pub type u32be = BigEndian; 15 | /// Shorthand for `LittleEndian` 16 | pub type u64le = LittleEndian; 17 | /// Shorthand for `BigEndian` 18 | pub type u64be = BigEndian; 19 | /// Shorthand for `LittleEndian` 20 | pub type u128le = LittleEndian; 21 | /// Shorthand for `BigEndian` 22 | pub type u128be = BigEndian; 23 | /// Shorthand for `LittleEndian` 24 | pub type usizele = LittleEndian; 25 | /// Shorthand for `BigEndian` 26 | pub type usizebe = BigEndian; 27 | 28 | /// Shorthand for `LittleEndian` 29 | pub type i16le = LittleEndian; 30 | /// Shorthand for `BigEndian` 31 | pub type i16be = BigEndian; 32 | /// Shorthand for `LittleEndian` 33 | pub type i32le = LittleEndian; 34 | /// Shorthand for `BigEndian` 35 | pub type i32be = BigEndian; 36 | /// Shorthand for `LittleEndian` 37 | pub type i64le = LittleEndian; 38 | /// Shorthand for `BigEndian` 39 | pub type i64be = BigEndian; 40 | /// Shorthand for `LittleEndian` 41 | pub type i128le = LittleEndian; 42 | /// Shorthand for `BigEndian` 43 | pub type i128be = BigEndian; 44 | /// Shorthand for `LittleEndian` 45 | pub type isizele = LittleEndian; 46 | /// Shorthand for `BigEndian` 47 | pub type isizebe = BigEndian; 48 | 49 | /// Shorthand for `LittleEndian` 50 | pub type f32le = LittleEndian; 51 | /// Shorthand for `BigEndian` 52 | pub type f32be = BigEndian; 53 | 54 | /// Shorthand for `LittleEndian` 55 | pub type f64le = LittleEndian; 56 | /// Shorthand for `BigEndian` 57 | pub type f64be = BigEndian; 58 | -------------------------------------------------------------------------------- /src/specific_endian.rs: -------------------------------------------------------------------------------- 1 | /// Any object implementing `SpecificEndian` can be converted between big and little endian. Implement this trait to allow for endian conversion by this crate. 2 | pub trait SpecificEndian 3 | where 4 | Self: Into + Clone + Copy, 5 | { 6 | fn to_big_endian(&self) -> T; 7 | fn to_little_endian(&self) -> T; 8 | fn from_big_endian(&self) -> T; 9 | fn from_little_endian(&self) -> T; 10 | } 11 | 12 | #[cfg(feature = "byte_impls")] 13 | mod byte_impls { 14 | use super::*; 15 | /// A macro implementing `SpecificEndian` for simple data types where big and little endian forms are the same. 16 | macro_rules! make_specific_endian_single_byte { 17 | ($wrap_ty:ty) => { 18 | impl SpecificEndian<$wrap_ty> for $wrap_ty { 19 | fn to_big_endian(&self) -> Self { 20 | *self 21 | } 22 | fn to_little_endian(&self) -> Self { 23 | *self 24 | } 25 | fn from_big_endian(&self) -> Self { 26 | *self 27 | } 28 | fn from_little_endian(&self) -> Self { 29 | *self 30 | } 31 | } 32 | }; 33 | } 34 | 35 | make_specific_endian_single_byte!(u8); 36 | make_specific_endian_single_byte!(i8); 37 | // If bool ends up being represented by something other than a byte, this might not work right. 38 | make_specific_endian_single_byte!(bool); 39 | } 40 | 41 | #[cfg(feature = "integer_impls")] 42 | mod integer_impls { 43 | use super::*; 44 | /// A macro for implementing `SpecificEndian` on types that have endian conversions built into Rust. Currently, this is the primitive integer types. 45 | macro_rules! make_specific_endian_integer { 46 | ($wrap_ty:ty) => { 47 | impl SpecificEndian<$wrap_ty> for $wrap_ty { 48 | fn to_big_endian(&self) -> Self { 49 | self.to_be() 50 | } 51 | fn to_little_endian(&self) -> Self { 52 | self.to_le() 53 | } 54 | fn from_big_endian(&self) -> Self { 55 | Self::from_be(*self) 56 | } 57 | fn from_little_endian(&self) -> Self { 58 | Self::from_le(*self) 59 | } 60 | } 61 | }; 62 | } 63 | 64 | make_specific_endian_integer!(u16); 65 | make_specific_endian_integer!(i16); 66 | make_specific_endian_integer!(u32); 67 | make_specific_endian_integer!(i32); 68 | make_specific_endian_integer!(u64); 69 | make_specific_endian_integer!(i64); 70 | make_specific_endian_integer!(u128); 71 | make_specific_endian_integer!(i128); 72 | make_specific_endian_integer!(usize); 73 | make_specific_endian_integer!(isize); 74 | } 75 | 76 | #[cfg(feature = "float_impls")] 77 | mod float_impls { 78 | use super::*; 79 | /// Uses .from_bits() and .to_bits() to implement SpecificEndian with Integer types. Can be used with any type having these methods, but mainly for use with the floats. 80 | macro_rules! make_specific_endian_float { 81 | ($wrap_ty:ty) => { 82 | impl SpecificEndian<$wrap_ty> for $wrap_ty { 83 | fn to_big_endian(&self) -> Self { 84 | Self::from_bits(self.to_bits().to_be()) 85 | } 86 | fn to_little_endian(&self) -> Self { 87 | Self::from_bits(self.to_bits().to_le()) 88 | } 89 | fn from_big_endian(&self) -> Self { 90 | Self::from_bits(self.to_bits().from_big_endian()) 91 | } 92 | fn from_little_endian(&self) -> Self { 93 | Self::from_bits(self.to_bits().from_little_endian()) 94 | } 95 | } 96 | }; 97 | } 98 | 99 | make_specific_endian_float!(f32); 100 | make_specific_endian_float!(f64); 101 | } 102 | 103 | /// A big-endian representation of type `T` that implements `SpecificEndian`. Data stored in the struct must be converted to big-endian using `::from()` or `.into()`. 104 | #[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq)] 105 | #[repr(transparent)] 106 | pub struct BigEndian>(pub(crate) T); 107 | 108 | impl BigEndian 109 | where 110 | T: SpecificEndian, 111 | { 112 | /// Returns the raw data stored in the struct. 113 | pub fn to_bits(&self) -> T { 114 | self.0 115 | } 116 | /// Imports the data raw into a BigEndian struct. 117 | pub fn from_bits(v: T) -> Self { 118 | Self(v) 119 | } 120 | /// Converts the data to the same type T in host-native endian. 121 | pub fn to_native(&self) -> T { 122 | T::from_big_endian(&self.0) 123 | } 124 | } 125 | 126 | impl> From for BigEndian { 127 | fn from(v: T) -> BigEndian { 128 | BigEndian::(v.to_big_endian()) 129 | } 130 | } 131 | 132 | /// A little-endian representation of type `T` that implements `SpecificEndian`. Data stored in the struct must be converted to little-endian using `::from()` or `.into()`. 133 | #[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq)] 134 | #[repr(transparent)] 135 | pub struct LittleEndian>(pub(crate) T); 136 | 137 | impl LittleEndian 138 | where 139 | T: SpecificEndian, 140 | { 141 | /// Returns the raw data stored in the struct. 142 | pub fn to_bits(&self) -> T { 143 | self.0 144 | } 145 | /// Imports the data raw into a LittleEndian struct. 146 | pub fn from_bits(v: T) -> Self { 147 | Self(v) 148 | } 149 | /// Converts the data to the same type T in host-native endian. 150 | pub fn to_native(&self) -> T { 151 | T::from_little_endian(&self.0) 152 | } 153 | } 154 | 155 | impl> From for LittleEndian { 156 | fn from(v: T) -> LittleEndian { 157 | LittleEndian::(v.to_little_endian()) 158 | } 159 | } 160 | 161 | #[cfg(feature = "big_endian")] 162 | mod big_endian_primatives { 163 | #[allow(unused_imports)] 164 | use super::*; 165 | // Rust's orphan trait rule prevents us from using a generic implementation on the primitive types, so we do this: 166 | #[allow(unused_macros)] 167 | macro_rules! make_primitive_type_from_be { 168 | ($wrap_ty:ty) => { 169 | impl From> for $wrap_ty { 170 | fn from(v: BigEndian<$wrap_ty>) -> $wrap_ty { 171 | v.0.from_big_endian() 172 | } 173 | } 174 | }; 175 | } 176 | 177 | #[cfg(feature = "integer_impls")] 178 | make_primitive_type_from_be!(bool); 179 | #[cfg(feature = "integer_impls")] 180 | make_primitive_type_from_be!(u8); 181 | #[cfg(feature = "integer_impls")] 182 | make_primitive_type_from_be!(i8); 183 | #[cfg(feature = "integer_impls")] 184 | make_primitive_type_from_be!(u16); 185 | #[cfg(feature = "integer_impls")] 186 | make_primitive_type_from_be!(i16); 187 | #[cfg(feature = "integer_impls")] 188 | make_primitive_type_from_be!(u32); 189 | #[cfg(feature = "integer_impls")] 190 | make_primitive_type_from_be!(i32); 191 | #[cfg(feature = "integer_impls")] 192 | make_primitive_type_from_be!(u64); 193 | #[cfg(feature = "integer_impls")] 194 | make_primitive_type_from_be!(i64); 195 | #[cfg(feature = "integer_impls")] 196 | make_primitive_type_from_be!(u128); 197 | #[cfg(feature = "integer_impls")] 198 | make_primitive_type_from_be!(i128); 199 | #[cfg(feature = "integer_impls")] 200 | make_primitive_type_from_be!(usize); 201 | #[cfg(feature = "integer_impls")] 202 | make_primitive_type_from_be!(isize); 203 | #[cfg(feature = "float_impls")] 204 | make_primitive_type_from_be!(f32); 205 | #[cfg(feature = "float_impls")] 206 | make_primitive_type_from_be!(f64); 207 | } 208 | 209 | #[cfg(feature = "little_endian")] 210 | mod little_endian_primatives { 211 | #[allow(unused_imports)] 212 | use super::*; 213 | // Rust's orphan trait rule prevents us from using a generic implementation on the primitive types, so we do this: 214 | #[allow(unused_macros)] 215 | macro_rules! make_primitive_type_from_le { 216 | ($wrap_ty:ty) => { 217 | impl From> for $wrap_ty { 218 | fn from(v: LittleEndian<$wrap_ty>) -> $wrap_ty { 219 | v.0.from_little_endian() 220 | } 221 | } 222 | }; 223 | } 224 | 225 | #[cfg(feature = "integer_impls")] 226 | make_primitive_type_from_le!(bool); 227 | #[cfg(feature = "integer_impls")] 228 | make_primitive_type_from_le!(u8); 229 | #[cfg(feature = "integer_impls")] 230 | make_primitive_type_from_le!(i8); 231 | #[cfg(feature = "integer_impls")] 232 | make_primitive_type_from_le!(u16); 233 | #[cfg(feature = "integer_impls")] 234 | make_primitive_type_from_le!(i16); 235 | #[cfg(feature = "integer_impls")] 236 | make_primitive_type_from_le!(u32); 237 | #[cfg(feature = "integer_impls")] 238 | make_primitive_type_from_le!(i32); 239 | #[cfg(feature = "integer_impls")] 240 | make_primitive_type_from_le!(u64); 241 | #[cfg(feature = "integer_impls")] 242 | make_primitive_type_from_le!(i64); 243 | #[cfg(feature = "integer_impls")] 244 | make_primitive_type_from_le!(u128); 245 | #[cfg(feature = "integer_impls")] 246 | make_primitive_type_from_le!(i128); 247 | #[cfg(feature = "integer_impls")] 248 | make_primitive_type_from_le!(usize); 249 | #[cfg(feature = "integer_impls")] 250 | make_primitive_type_from_le!(isize); 251 | #[cfg(feature = "float_impls")] 252 | make_primitive_type_from_le!(f32); 253 | #[cfg(feature = "float_impls")] 254 | make_primitive_type_from_le!(f64); 255 | } 256 | 257 | #[cfg(feature = "both_endian")] 258 | mod both_endian_primatives { 259 | use super::*; 260 | /// Allow conversion directly from `LittleEndian` to `BigEndian` without manually going through native endian. 261 | impl> From> for BigEndian { 262 | fn from(v: LittleEndian) -> BigEndian { 263 | BigEndian::::from(v.to_native()) 264 | } 265 | } 266 | 267 | /// Allow conversion directly from `BigEndian` to `LittleEndian` without manually going through native endian. 268 | impl> From> for LittleEndian { 269 | fn from(v: BigEndian) -> LittleEndian { 270 | LittleEndian::::from(v.to_native()) 271 | } 272 | } 273 | } 274 | 275 | #[cfg(test)] 276 | mod tests { 277 | use crate::*; 278 | use core::mem::size_of; 279 | 280 | #[test] 281 | fn declare_all() { 282 | let _a: BigEndian = 0xfe.into(); 283 | let _a: LittleEndian = 0xfe.into(); 284 | let _a: BigEndian = 0xfe.into(); 285 | let _a: LittleEndian = 0xfe.into(); 286 | 287 | let _a: BigEndian = 0xfe.into(); 288 | let _a: LittleEndian = 0xfe.into(); 289 | let _a: BigEndian = 0xfe.into(); 290 | let _a: LittleEndian = 0xfe.into(); 291 | 292 | let _a: BigEndian = 0xfe.into(); 293 | let _a: LittleEndian = 0xfe.into(); 294 | let _a: BigEndian = 0xfe.into(); 295 | let _a: LittleEndian = 0xfe.into(); 296 | 297 | let _a: BigEndian = 0xfe.into(); 298 | let _a: LittleEndian = 0xfe.into(); 299 | let _a: BigEndian = 0xfe.into(); 300 | let _a: LittleEndian = 0xfe.into(); 301 | } 302 | 303 | #[test] 304 | fn make_struct() { 305 | #[repr(C)] 306 | struct Foo( 307 | BigEndian, 308 | LittleEndian, 309 | BigEndian, 310 | LittleEndian, 311 | BigEndian, 312 | LittleEndian, 313 | BigEndian, 314 | LittleEndian, 315 | BigEndian, 316 | LittleEndian, 317 | BigEndian, 318 | LittleEndian, 319 | BigEndian, 320 | LittleEndian, 321 | BigEndian, 322 | LittleEndian, 323 | BigEndian, 324 | LittleEndian, 325 | BigEndian, 326 | LittleEndian, 327 | ); 328 | 329 | let _foo = Foo( 330 | 0.into(), 331 | 1.into(), 332 | 2.into(), 333 | 3.into(), 334 | 4.into(), 335 | 5.into(), 336 | 6.into(), 337 | 7.into(), 338 | 8.into(), 339 | 9.into(), 340 | 10.into(), 341 | 11.into(), 342 | 12.into(), 343 | 13.into(), 344 | 14.into(), 345 | 15.into(), 346 | (0.1).into(), 347 | (123.5).into(), 348 | (7.8).into(), 349 | (12345.4567).into(), 350 | ); 351 | } 352 | 353 | #[test] 354 | fn store_be() { 355 | let be: BigEndian = 0xfe.into(); 356 | if cfg!(byte_order = "big endian") { 357 | assert_eq!(be.to_bits(), 0xfe); 358 | } else { 359 | assert_eq!(be.to_bits(), 0xfe00000000000000); 360 | } 361 | } 362 | 363 | #[test] 364 | fn same_size() { 365 | assert_eq!(size_of::(), size_of::()); 366 | } 367 | 368 | #[test] 369 | fn store_le() { 370 | let le: LittleEndian = 0xfe.into(); 371 | if cfg!(byte_order = "big endian") { 372 | assert_eq!(le.to_bits(), 0xfe00000000000000); 373 | } else { 374 | assert_eq!(le.to_bits(), 0xfe); 375 | } 376 | } 377 | 378 | #[test] 379 | fn cast() { 380 | let be = BigEndian::from(12345); 381 | let ne: u64 = be.into(); 382 | assert_eq!(ne, 12345); 383 | } 384 | 385 | #[test] 386 | fn convert_back() { 387 | let be = BigEndian::from(12345); 388 | println!("{}", u64::from(be)); 389 | } 390 | 391 | #[test] 392 | fn convert_to_native() { 393 | let be = BigEndian::from(0xfe); 394 | println!("{:x}, {:x}", be.0, be.to_native()); 395 | assert_eq!(0xfe, be.to_native()); 396 | } 397 | 398 | #[test] 399 | fn store_fp_be() { 400 | let be1 = BigEndian::::from(1234.5678); 401 | if cfg!(byte_order = "little endian") { 402 | assert_ne!(1234.5678, be1.to_bits()); 403 | } 404 | assert_eq!(1234.5678, f64::from(be1)); 405 | } 406 | 407 | #[test] 408 | fn store_fp_le() { 409 | let le1 = LittleEndian::::from(1234.5678); 410 | if cfg!(byte_order = "big endian") { 411 | assert_ne!(1234.5678, le1.to_bits()); 412 | } 413 | assert_eq!(1234.5678, f64::from(le1)); 414 | } 415 | 416 | #[test] 417 | fn inferred_type() { 418 | let mut be1 = BigEndian::from(1234); 419 | be1 &= BigEndian::from(5678); 420 | println!("{} {} {}", be1, be1.to_bits(), be1.to_native()); 421 | assert_eq!(be1, 1026.into()); 422 | } 423 | 424 | #[test] 425 | fn inferred_type_fp() { 426 | let mut be1 = BigEndian::from(1234.5); 427 | be1 += BigEndian::from(5678.1); 428 | println!("{} {} {}", be1, be1.to_bits(), be1.to_native()); 429 | assert_eq!(be1, 6912.6.into()); 430 | } 431 | 432 | #[test] 433 | fn inferred_type_bigger() { 434 | let mut be1 = BigEndian::from(0x0feeddcc); 435 | be1 &= BigEndian::from(0xff00); 436 | println!("{} {} {}", be1, be1.to_bits(), be1.to_native()); 437 | assert_eq!(be1, 0xdd00.into()); 438 | } 439 | 440 | #[test] 441 | fn mixed_endian_big() { 442 | let be = BigEndian::from(100); 443 | let le = LittleEndian::from(200); 444 | let me = be + le.into(); 445 | assert_eq!(me, 300.into()); 446 | } 447 | 448 | #[test] 449 | fn mixed_endian_little() { 450 | let be = BigEndian::from(100); 451 | let le = LittleEndian::from(200); 452 | let me = le + be.into(); 453 | assert_eq!(me, 300.into()); 454 | } 455 | 456 | #[test] 457 | fn custom_type() { 458 | #[derive(Copy, Clone, Debug)] 459 | enum EndianAwareExample { 460 | BigEndianFunction(u64), 461 | LittleEndianFunction(u64), 462 | } 463 | impl SpecificEndian for EndianAwareExample { 464 | fn to_big_endian(&self) -> Self { 465 | match self { 466 | EndianAwareExample::BigEndianFunction(_) => *self, 467 | EndianAwareExample::LittleEndianFunction(v) => { 468 | EndianAwareExample::BigEndianFunction(v.to_big_endian()) 469 | } 470 | } 471 | } 472 | fn to_little_endian(&self) -> Self { 473 | match self { 474 | EndianAwareExample::LittleEndianFunction(_0) => *self, 475 | EndianAwareExample::BigEndianFunction(v) => { 476 | EndianAwareExample::BigEndianFunction(v.to_little_endian()) 477 | } 478 | } 479 | } 480 | fn from_big_endian(&self) -> Self { 481 | match self { 482 | EndianAwareExample::BigEndianFunction(_0) => *self, 483 | EndianAwareExample::LittleEndianFunction(v) => { 484 | EndianAwareExample::BigEndianFunction(v.to_big_endian()) 485 | } 486 | } 487 | } 488 | fn from_little_endian(&self) -> Self { 489 | match self { 490 | EndianAwareExample::LittleEndianFunction(_) => *self, 491 | EndianAwareExample::BigEndianFunction(v) => { 492 | EndianAwareExample::BigEndianFunction(v.to_little_endian()) 493 | } 494 | } 495 | } 496 | } 497 | let foo: BigEndian = 498 | EndianAwareExample::LittleEndianFunction(0xf0).into(); 499 | #[allow(unused_assignments)] 500 | let mut value = 0; 501 | match foo.to_native() { 502 | EndianAwareExample::BigEndianFunction(v) => { 503 | println!("be: {:x}", v); 504 | value = v 505 | } 506 | EndianAwareExample::LittleEndianFunction(v) => { 507 | println!("le: {:x}", v); 508 | value = 0 509 | } 510 | } 511 | assert_eq!(value, 0x0f000000000000000); 512 | } 513 | } 514 | --------------------------------------------------------------------------------