Open30

RustTips

9kv8xiyi9kv8xiyi

2つの変数の中身を交換したい場合、普通は

let tmp=a;
a=b;
b=tmp;

とするが、rustの場合、tupleを使えば

(a,b)=(b,a);

と、1行で済ませられる。

9kv8xiyi9kv8xiyi

src/内にlib.rsmain.rsが両方ある場合、二つは別々のcrateとして扱われる

9kv8xiyi9kv8xiyi

zip()を使うと二つの配列(等)をタプルのiteratorとして扱える

let x=vec![0,1,2,3,4];
let y=vec![4,3,2,1,0];
for (i,j) in x.zip(y){
   println!("{i},{j}");
}
/* this prints
0,4
1,3
2,2
3,1
4,0
*/

9kv8xiyi9kv8xiyi

charからu8に変換したい場合 as以外に

let letters=b'z'-b'a';

とする方法がある

9kv8xiyi9kv8xiyi

iterationをしているときに所有権が必要な場合iter()methodではなくinto_iter()methodを使う

9kv8xiyi9kv8xiyi

iteratorのfold()methodを使うことで楽に新しい値を作ることができる
例:leet code 49

const ALPHABETS: usize = (b'z' - b'a' + 1) as _;
impl Solution {
	pub fn group_anagrams(mut strs: Vec<String,>,) -> Vec<Vec<String,>,> {
		strs.into_iter()
			.fold(HashMap::<[u8; ALPHABETS], Vec<String,>,>::new(), |mut map, s| {
				let freqs = s.bytes().map(|b| (b - b'a') as usize,).fold(
					[0; ALPHABETS],
					|mut freqs, bin| {
						freqs[bin] += 1;
						freqs
					},
				);
				map.entry(freqs,).or_default().push(s,);
				map
			},)
			.into_values()
			.collect()
	}
}
9kv8xiyi9kv8xiyi
if boolean {
   1
} else {
   0
}

というコードは::from(T)methodを使って短縮できる
例:

vec![6,7][usize::from(true)]==7
vec![6,7][usize::from(false)]==6
9kv8xiyi9kv8xiyi

HashMapでよくやる操作の一つ、もしkeyが存在したら+1 (or other operation)、しなかったら特定の値を代入。これは

t_chars.entry(key,).and_modify(|i| *i += 1,).or_insert(1);

のように、and_modifyor_insertを使った方法がよく取られるが、

t_chars.entry(key,).or_insert(0,) += 1;

とした方が高速。

9kv8xiyi9kv8xiyi

format modifier

format!マクロではformat modifierが使える。

let printout = "printout";
assert_eq!(format!("{printout:?}"), "\"printout\"");
9kv8xiyi9kv8xiyi

format modifierのpを使うと、変数のアドレスを確認できる

let x = &42;
let address = format!("{x:p}"); // this produces something like '0x7f06092ac6d0'
9kv8xiyi9kv8xiyi

小数点以下の桁数を指定したいときは.4などとすればできる

assert_eq!(format!("{:.4}", 10.0), "10.0000");
9kv8xiyi9kv8xiyi

rustでは関数やクロージャの型はお互いに固有なものになっている。

fn closure_ornot<GenT: 'static,>(_which: GenT,) -> &'static str {
	let gen_id = TypeId::of::<GenT,>();
	if TypeId::of::<i32,>() == gen_id {
		"GenT is i32"
	} else if TypeId::of::<dyn Fn() -> i32,>() == gen_id {
		"GenT is Fn()->i32"
	} else {
		"unexpected!!"
	}
}

assert_eq!(closure_ornot(return_closure()(),), "GenT is i32");
assert_eq!(closure_ornot(return_closure(),), "unexpected!!");

fn fst_citizen() -> i32 { 0 }
assert_eq!(closure_ornot(fst_citizen),"unexpected!!");
9kv8xiyi9kv8xiyi

こっちの方がわかりやすいかも

// call's "anonymous type"
let cl1 = || 1;
let cl2 = || 2;
assert!(cl1.type_id() != cl2.type_id());

//fn's type is as well
fn ret1() -> i32 { 1 }
fn ret2() -> i32 { 2 }
fn retn() -> i32 { 1 }
assert!(ret1.type_id() != ret2.type_id());
assert!(ret1.type_id() != retn.type_id());
9kv8xiyi9kv8xiyi

traitは標準ライブラリが提供している型はもちろん組み込み型にも実装できる。

trait MyName {
	fn is(&self,) -> &str;
}

impl<T,> MyName for Vec<T,> {
	fn is(&self,) -> &str { "!Vec<T>!" }
}

impl MyName for i32 {
	fn is(&self,) -> &str { "!int!" }
}

impl<T,> MyName for (i32, Vec<T,>,) {
	fn is(&self,) -> &str { "!(i32, Vec<T>)!" }
}

let v = vec![0, 1, 1, 2, 23];
let ai = 0;
assert_eq!(v.is(), "!Vec<T>!");
assert_eq!(ai.is(), "!int!");
assert_eq!((ai, v,).is(), "!(i32, Vec<T>)!");
9kv8xiyi9kv8xiyi

Option<T>

Option<T>型はiteratorにできる(なぜ???)

let a = Some("a",);
for &i in a.iter() {
	assert_eq!(i, "a");
}
9kv8xiyi9kv8xiyi

Option<T>::map()に渡されるクロージャのとる引数はOption<T>型ではなくT型。
何を言っているのかわからねーと思うが自分も何を言っているのかわからなかったので例:

None.map(|_one: i32| panic!("This painc won't be executed"),);
Some(1,).map(|one| assert!(one == 1),);
9kv8xiyi9kv8xiyi

cargo testで出力を見たい時はcargo test -- --nocaptureとするのが一般的だが、-- --nocaptureと指定するのが面倒な場合

	#[cfg(test)]
	mod tests {
		#[test]
		fn box_test() {
			let b = Box::new(1,);
			assert!(false, "{b:?}");
		}
	}

のようにassert!(false, "some output")
とすると便利。

9kv8xiyi9kv8xiyi

if文の代わりにbool::then()を使うとrubyのようにかける

let mut a = 0;
true.then(|| { a += 1; });
false.then(|| { a += 1; });
assert_eq!(a, 1);

let Some(msg) = return_boolean().then(|| "returned true") else {
   panic("returned false");
};
assert_eq!(msg, "returned true");
9kv8xiyi9kv8xiyi

Iterator::flat_map()はnestした型を均したい時に便利

let vector = vec![0, 1, 2];
let from_map: Vec<u8,> = vector.iter().map(|n| n * 2,).collect();
let vecvec = vec![vector.clone(); 3];
let from_flat_map: Vec<u8,> = vecvec.iter().flat_map(|i| i.clone(),).collect();
assert_eq!(from_map, [0, 2, 4]);
assert_eq!(from_flat_map, [0, 1, 2, 0, 1, 2, 0, 1, 2]);
9kv8xiyi9kv8xiyi

labelから値を返すことができるようになった(from rust 1.65.0)
statement用のearly returnとでも言いましょうか

let rslt = 'b: {
	if false {
		break 'b 1;
	}
                     
	if true {
		break 'b 2;
	}
	3
};
assert_eq!(rslt, 2);
9kv8xiyi9kv8xiyi

rustでもscientific notationは使えまぁす

let a = 1e5 as i32;
assert_eq!(a, 100000);
9kv8xiyi9kv8xiyi

rustにはサイズが0の型がある(zst stands for Zero Sized Type)
()なんかはzstの代表的な例
これが何の役に立つかというと
Set<key>Map<key,()>のラッパーとして実装するときに()がzstなので余分なメモリを確保しなくて済むのだそうだ

以下公式ドキュメントからの引用

#![allow(unused)]
fn main() {
struct Foo; // フィールドがない = サイズ 0

// すべてのフィールドのサイズがない = サイズ 0
struct Baz {
    foo: Foo,
    qux: (),      // 空のタプルにはサイズがありません
    baz: [u8; 0], // 空の配列にはサイズがありません
}
}
9kv8xiyi9kv8xiyi

デバッグ情報として、現在実行されている関数へのパスも表示したい時がある
そんな時はmodule_path!()std::any::type_name::<fn()>()で解決できる
例↓

println!("current path is: {}::{}", module_path!(), std::any::type_name::<fn()>());
9kv8xiyi9kv8xiyi

rustでバイナリ表示の際の桁数を求めたい時、leading_zeros()メソッドが便利

fn binary_digit_count_bitwise(num: u32) -> u32 {
    32 - num.leading_zeros()
}

fn main() {
    let number = 42;
    let count = binary_digit_count_bitwise(number);
    println!("The binary digit count of {} is {}", number, count);
}
9kv8xiyi9kv8xiyi

ベクトルの要素を取り除く関数たちのパフォーマンス比較 by gpt君

How drain() Works
Definition:
pub fn drain<R>(&mut self, range: R) -> Drain<'_, T>
where
R: RangeBounds<usize>;
Purpose:
Extracts elements from the vector, either in a specific range or for the entire vector (default if no range is provided).
It operates by creating an iterator over the specified range and removes the elements from the vector once the iterator is dropped.
Key Features:
Avoids cloning: It moves the elements instead of cloning them.
Destructive: The vector shrinks, as the drained range is removed.
Efficient for partial removals: It doesn’t reallocate memory unnecessarily, especially if the drained range is at the beginning or end of the vector.
Performance Comparison

  1. drain() vs retain()

retain():
Keeps elements in the vector based on a predicate.
Requires evaluating every element, which can be slower for simple removal tasks.
Retains elements in place without creating an iterator.
Performance:
drain() is faster if you know exactly which range you want to remove.
retain() may be better when removing elements based on a condition.
2. drain() vs split_off()

split_off():
Splits the vector into two at a given index, returning the second half while leaving the first half intact.
Useful for separating at a specific index, but less versatile than drain() for arbitrary ranges.
Performance:
split_off() is faster for separating into halves because it avoids shifting elements unnecessarily.
drain() is more flexible but can involve additional overhead for ranges not aligned with the vector's layout.
3. drain() vs Direct Indexing

Direct Indexing:
Removing elements using methods like vec.remove() in a loop.
Works for small, specific removals but inefficient for large ranges, as each remove() call shifts all subsequent elements.
Performance:
drain() is significantly faster for ranges, as it minimizes shifting operations and manages memory efficiently.
4. drain() vs Iteration with extend()

Iteration with extend():
Using iter() or into_iter() to collect and create a new vector without specific elements.
This requires allocating a new vector, making it slower and more memory-intensive.
Performance:
drain() is more memory-efficient since it modifies the collection in place.