Rust中为兼容旧版本的泛型BitVec初始化方法命名方案咨询
Rust中为兼容旧版本的泛型BitVec初始化方法命名方案咨询
我目前在维护bit-vec这个Rust crate,它是类似C++std::vector<bool>的动态位数组。最近我们遇到了一个需求:要为任意实现BitBlock trait的BitVec<B>添加初始化方法,但如果直接把现有的方法改成泛型,会破坏类型推断——比如原来简单的BitVec::new()现在会抛出类型注解缺失的错误:
struct BitVec<B> { store: Vec<B> } impl<B> BitVec<B> { fn new() -> Self { Self { store: Vec::new() } } } fn main() { let v = BitVec::new(); println!("Hello, world!"); }
对应的错误信息:
error[E0282]: type annotations needed for `BitVec<_>` --> src/main.rs:12:9 | 12 | let v = BitVec::new(); | ^ ------------- type must be known at this point | help: consider giving `v` an explicit type, where the type for type parameter `B` is specified
虽然我们的库现在处于semver v0.8版本,理论上可以做破坏性变更,但我在纠结这么做是否值得。目前的折中方案是保留原有针对BitVec<u32>的初始化方法,同时新增一套泛型版本的方法,但卡在了命名上——是用new_general/from_elem_general这类后缀命名,还是generic_new/generic_from_elem,甚至是new_in_general?
下面是我们目前的实现代码:
impl BitVec<u32> { /// Creates an empty `BitVec`. /// /// # Examples /// /// ``` /// use bit_vec::BitVec; /// let mut bv = BitVec::new(); /// ``` #[inline] pub fn new() -> Self { Default::default() } /// Creates a `BitVec` that holds `nbits` elements, setting each element /// to `bit`. /// /// # Examples /// /// ``` /// use bit_vec::BitVec; /// /// let mut bv = BitVec::from_elem(10, false); /// assert_eq!(bv.len(), 10); /// for x in bv.iter() { /// assert_eq!(x, false); /// } /// ``` #[inline] pub fn from_elem(len: usize, bit: bool) -> Self { BitVec::<u32>::from_elem_general(len, bit) } /// Constructs a new, empty `BitVec` with the specified capacity. /// /// The bitvector will be able to hold at least `capacity` bits without /// reallocating. If `capacity` is 0, it will not allocate. /// /// It is important to note that this function does not specify the /// *length* of the returned bitvector, but only the *capacity*. #[inline] pub fn with_capacity(capacity: usize) -> Self { BitVec::<u32>::with_capacity_general(capacity) } /// Transforms a byte-vector into a `BitVec`. Each byte becomes eight bits, /// with the most significant bits of each byte coming first. Each /// bit becomes `true` if equal to 1 or `false` if equal to 0. /// /// # Examples /// /// ``` /// use bit_vec::BitVec; /// /// let bv = BitVec::from_bytes(&[0b10100000, 0b00010010]); /// assert!(bv.eq_vec(&[true, false, true, false, /// false, false, false, false, /// false, false, false, true, /// false, false, true, false])); /// ``` pub fn from_bytes(bytes: &[u8]) -> Self { BitVec::<u32>::from_bytes_general(bytes) } /// Creates a `BitVec` of the specified length where the value at each index /// is `f(index)`. /// /// # Examples /// /// ``` /// use bit_vec::BitVec; /// /// let bv = BitVec::from_fn(5, |i| { i % 2 == 0 }); /// assert!(bv.eq_vec(&[true, false, true, false, true])); /// ``` #[inline] pub fn from_fn<F>(len: usize, f: F) -> Self where F: FnMut(usize) -> bool, { BitVec::<u32>::from_fn_general(len, f) } } impl<B: BitBlock> BitVec<B> { /// Creates an empty `BitVec`. /// /// # Examples /// /// ``` /// use bit_vec::BitVec; /// let mut bv = BitVec::<usize>::new_general(); /// ``` #[inline] pub fn new_general() -> Self { Default::default() } /// Creates a `BitVec` that holds `nbits` elements, setting each element /// to `bit`. /// /// # Examples /// /// ``` /// use bit_vec::BitVec; /// /// let mut bv = BitVec::<usize>::from_elem_general(10, false); /// assert_eq!(bv.len(), 10); /// for x in bv.iter() { /// assert_eq!(x, false); /// } /// ``` #[inline] pub fn from_elem_general(len: usize, bit: bool) -> Self { let nblocks = blocks_for_bits::<B>(len); let mut bit_vec = BitVec { storage: vec![if bit { !B::zero() } else { B::zero() }; nblocks], nbits: len, }; bit_vec.fix_last_block(); bit_vec } /// Constructs a new, empty `BitVec` with the specified capacity. /// /// The bitvector will be able to hold at least `capacity` bits without /// reallocating. If `capacity` is 0, it will not allocate. /// /// It is important to note that this function does not specify the /// *length* of the returned bitvector, but only the *capacity*. #[inline] pub fn with_capacity_general(capacity: usize) -> Self { BitVec { storage: Vec::with_capacity(blocks_for_bits::<B>(capacity)), nbits: 0, } } /// Transforms a byte-vector into a `BitVec`. Each byte becomes eight bits, /// with the most significant bits of each byte coming first. Each /// bit becomes `true` if equal to 1 or `false` if equal to 0. /// /// # Examples /// /// ``` /// use bit_vec::BitVec; /// /// let bv = BitVec::<usize>::from_bytes_general(&[0b10100000, 0b00010010]); /// assert!(bv.eq_vec(&[true, false, true, false, /// false, false, false, false, /// false, false, false, true, /// false, false, true, false])); /// ``` pub fn from_bytes_general(bytes: &[u8]) -> Self { let len = bytes .len() .checked_mul(u8::bits()) .expect("capacity overflow"); let mut bit_vec = BitVec::with_capacity_general(len); let complete_words = bytes.len() / B::bytes(); let extra_bytes = bytes.len() % B::bytes(); bit_vec.nbits = len; for i in 0..complete_words { let mut accumulator = B::zero(); for idx in 0..B::bytes() { accumulator |= B::from_byte(reverse_bits(bytes[i * B::bytes() + idx])) << (idx * 8) } bit_vec.storage.push(accumulator); } if extra_bytes > 0 { let mut last_word = B::zero(); for (i, &byte) in bytes[complete_words * B::bytes()..].iter().enumerate() { last_word |= B::from_byte(reverse_bits(byte)) << (i * 8); } bit_vec.storage.push(last_word); } bit_vec } /// Creates a `BitVec` of the specified length where the value at each index /// is `f(index)`. /// /// # Examples /// /// ``` /// use bit_vec::BitVec; /// /// let bv = BitVec::<usize>::from_fn_general(5, |i| { i % 2 == 0 }); /// assert!(bv.eq_vec(&[true, false, true, false, true])); /// ``` #[inline] pub fn from_fn_general<F>(len: usize, mut f: F) -> Self where F: FnMut(usize) -> bool, { let mut bit_vec = BitVec::from_elem_general(len, false); for i in 0..len { bit_vec.set(i, f(i)); } bit_vec } }
我的建议
从Rust社区的命名惯例和API可读性角度出发,我更推荐**_general后缀**的命名方案,理由如下:
- 符合社区习惯:Rust中常用后缀来区分同一功能的不同变体(比如
_unchecked、_mut),_general能清晰传达“支持更多类型的通用版本”这个含义; - 可读性更好:核心动作(
new、from_elem)放在方法名开头,用户查找时能快速定位,而generic_new这类前缀式命名会掩盖核心动作; - 一致性强:所有泛型方法统一使用
_general后缀,用户能快速识别出哪些是通用版本,降低学习成本; - 简洁直观:
new_in_general这类名字略显拗口,不如new_general简洁。
另外关于破坏性变更的问题:如果你的库有大量依赖用户,保留现有BitVec<u32>的方法是更稳妥的选择,避免突然打断用户的代码;如果用户群体较小,或者你希望推动用户迁移到泛型版本,可以考虑在v0.9版本做破坏性变更,将原有方法泛型化,同时引导用户添加类型注解或者提供默认类型别名。
备注:内容来源于stack exchange,提问作者Peter Blackson
相关产品推荐
相关产品推荐

