- 基础篇
fn main() {
//(1)最原始直接基础的位操作方法。
let mut byte: u8 = 0b0000_0000;
println!("0b{:08b}", byte);
byte |= 0b0000_1000; // Set a bit
println!("0b{:08b}", byte);
byte &= 0b1111_0111; // Unset a bit
println!("0b{:08b}", byte);
byte ^= 0b0000_1000; // Toggle a bit
println!("0b{:08b}", byte);
byte = !byte; // Flip all bits
println!("0b{:08b}", byte);
byte <<= 1; // shift left one bit
println!("0b{:08b}", byte);
byte >>= 1; // shift right one bit
println!("0b{:08b}", byte);
//特别提醒:rust为每一个数字类型都实现了大量方法,其中包括位操作方法!!!具体请参看下方链接!!!
//https://doc.rust-lang.org/std/primitive.u8.html
let mut rbyte: u8 = 0b1000_0000;
rbyte = rbyte.rotate_left(1); // rotate left one bit
println!("0b{:08b}", byte);
//https://doc.rust-lang.org/std/#primitives
rbyte = rbyte.rotate_right(1); // rotate right one bit
println!("0b{:08b}", rbyte);
bit_twiddling(0, 3);
bit_twiddling(8, 3);
//test bitwise operation macros
assert_eq!(eq1!(0b0000_1111, 0), true);
assert_eq!(eq0!(0b0000_1111, 4), true);
assert_eq!(set!(0b0000_1111, 0), 0x0f);
assert_eq!(clr!(0b0000_1111, 0), 0x0e);
}
//(2)定义为一个rust函数
fn bit_twiddling(original: u8, bit: u8) {
let mask = 1 << bit;
println!(
"Original: {:b}, Set: {:b}, Cleared: {:b}, Toggled: {:b}",
original,
original | mask,
original & !mask,
original ^ mask
);
}
//define rust macro for bitwise operations
//(3)定义为rust 宏
#[macro_export]
macro_rules! eq1 {
($n:expr, $b:expr) => {
$n & (1 << $b) != 0
};
}
#[macro_export]
macro_rules! eq0 {
($n:expr, $b:expr) => {
$n & (1 << $b) == 0
};
}
#[macro_export]
macro_rules! set {
($n:expr, $b:expr) => {
$n | (1 << $b)
};
}
#[macro_export]
macro_rules! clr {
($n:expr, $b:expr) => {
$n & !(1 << $b)
};
}
从C/C++一脉相传,位操作基本就是上面的样子!与或非,左移位、右移位等的组合。直说缺点吧,不好记忆,容易混淆!!!实际的嵌入式编程时,可能需要应对非常多的寄存器和每个寄存器bits的的映设关系!一旦出错不好排查!所以大家就想如果可以将位操作和rust的类型系统绑定起来,抽象封装成一个个类型和有意义的名字, 将映设关系固化下来,并且自动完成转化!从而增强语义和表达力,这样会很好用且容易排查错误!所以随后介绍的一些crates就是这方面的努力成果。
[Crate bitflags] map struct to a bit flag set
use std::fmt;
#[macro_use]
extern crate bitflags;
bitflags! {
pub struct Flags: u32 {//模块可见性: private默认本模块可见, pub模块外也可见。
const A = 0b00000001;
const B = 0b00000010;
const C = 0b00000100;
const ABC = Self::A.bits | Self::B.bits | Self::C.bits;
}
}
impl Flags {
pub fn clear(&mut self) {
self.bits = 0; // The `bits` field can be accessed from within the
// same module where the `bitflags!` macro was invoked.
}
}
impl fmt::Display for Flags {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "hi!")
}
}
fn main() {
//example1
let e1 = Flags::A | Flags::C;
let e2 = Flags::B | Flags::C;
assert_eq!((e1 | e2), Flags::ABC); // union
assert_eq!((e1 & e2), Flags::C); // intersection
assert_eq!((e1 - e2), Flags::A); // set difference
assert_eq!(!e2, Flags::A); // set complement
assert!(e1.contains(Flags::A));
//example2
let mut flags = Flags::A | Flags::B;
flags.clear();
assert!(flags.is_empty());
assert_eq!(format!("{}", flags), "hi!");
assert_eq!(format!("{:?}", Flags::A | Flags::B), "A | B");
assert_eq!(format!("{:?}", Flags::B), "B");
}
Crate [bitflags](https://docs.rs/bitflags/1.2.1/bitflags/)
此Rust Crate可以将一个struct
转化为一个bit flags set
, 自动完成映设和转化, 此处代码例子出自它的文档, 若要深入了解可去详细阅读之。
[Crate enumflags2] map a enum to a bit flags set
use enumflags2::BitFlags;
#[derive(BitFlags, Copy, Clone, Debug, PartialEq)]
#[repr(u8)]
enum Test {
A = 0b0001,
B = 0b0010,
C = 0b0100,
D = 0b1000,
}
fn main() {
let a_b = Test::A | Test::B; // BitFlags<Test>
let a_c = Test::A | Test::C;
let b_c_d = Test::C | Test::B | Test::D;
// BitFlags<Test>(0b11, [A, B])
println!("{:?}", a_b);
// BitFlags<Test>(0b1, [A])
println!("{:?}", a_b & a_c);
// Iterate over the flags like a normal set!
assert_eq!(a_b.iter().collect::<Vec<_>>(), &[Test::A, Test::B]);
assert!(a_b.contains(Test::A));
assert!(b_c_d.contains(Test::B | Test::C));
assert!(!(b_c_d.contains(a_b)));
assert!(a_b.intersects(a_c));
assert!(!(a_b.intersects(Test::C | Test::D)));
}
https://docs.rs/enumflags2/0.6.4/enumflags2/
此crate 将一个rust enum
映设为一个bit flags set!
此处代码例子出自它的文档, 若要深入了解可去详细阅读之。看来一定的抽象和封装是应对复杂和繁琐的有效手段!一定的抽象和封装使枯燥繁琐的位操作具有更好的可读性,更强的表达能力, 类型和命名是个好武器,将位映设关系固化自动化!从而降低使用难度,降低出错率。
但是人们没有就此止步,因为实际嵌入式编程中,我们直接与一个个
寄存器
交互, 从而控制硬件!所以寄存器
只需暴露一个个API
就好,而其内部的位操作和映设关系最好封装起来,外部无需了解!从而降低使用复杂度!实际编码中,我们只需要定义好一个个寄存器
, 然后调用其API
就好, 清晰明确,不易出错,介绍一个这样的rust crate
:bounded_registers
,[bounded_registers](https://docs.rs/bounded-registers/0.1.1/bounded_registers/)
, 其设计了形式化的格式来定义每一个寄存器
, 然后定义了统一的方法操作寄存器
的状态值,若要深入了解,请参阅其文档。
- 结束语
我们经常说,计算机编程领域应对复杂繁琐的有效武器之一就是:“增加一层,将复杂和繁琐抽象出来,封装起来形成一层代理”
上面的代码例子都比较直观,无需我多言,您若要深入研究,可参阅其原始文档和代码,本文只当抛砖引玉而已!
- 作者
学习随笔,如有谬误,望请海涵雅正,谢谢。
作者:心尘了
email: 285779289@qq.com git: https://github.com/yujinliang
- Reference
https://opensource.com/article/20/1/c-vs-rust-abstractions
https://docs.rs/bitflags/1.2.1/bitflags/
https://docs.rs/bounded-registers/0.1.1/bounded_registers/
https://stackoverflow.com/questions/43509560/how-to-implement-bitwise-operations-on-a-bitflags-enum
https://docs.rs/enumflags2/0.6.4/enumflags2/
https://stackoverflow.com/questions/40467995/how-do-you-set-clear-and-toggle-a-single-bit-in-rust
https://doc.rust-lang.org/std/primitive.i8.html
https://doc.rust-lang.org/std/primitive.u8.html
https://doc.rust-lang.org/std/#primitives
https://doc.rust-lang.org/book/appendix-02-operators.html
https://github.com/rsdump/rit
https://doc.rust-lang.org/reference/expressions/operator-expr.html#arithmetic-and-logical-binary-operators
https://rust-lang-nursery.github.io/rust-cookbook/data_structures/bitfield.html
评论区
写评论学到了