前回にてASCII変換処理はオリジナルの関数to_ascii_9()を使用することで落ち着きました。
次にこの関数を呼び出すことを考えましょう。
16進数に変換するto_hex()と同じ呼び出し方にすれば良いので下記のようになると思います。
pub(crate) fn to_hex(buf: &[u8]) -> String {
let sep = String::from(" ");
let hex = buf
.iter()
.map(|x| format!("{:02X}", x))
.collect::<Vec<_>>()
.join(&sep);
// dbg!(&hex);
hex
}
pub(crate) fn to_lines(buf: &[u8], len: usize) -> Vec<Line> {
let mut vec = Vec::new();
buf.chunks(len).for_each(|x| {
vec.push(Line::from(format!(
"{:width$} {}",
" ",
self::to_hex(x),
width = 8
)))
});
// dbg!(&vec);
vec
}
pub(crate) fn to_ascii_9(buf: &[u8]) -> String {
let res: String = buf
.iter()
.map(|&x| format!("{}", ToPrintableAscii::as_printable_char(x)))
.collect();
res
}
pub(crate) fn to_lines_ascii(buf: &[u8], len: usize) -> Vec<Line> {
let mut vec = Vec::new();
buf.chunks(len).for_each(|x| {
vec.push(Line::from(format!(
"{:width$} {}",
" ",
self::to_ascii_9(x),
width = 8
)))
});
// dbg!(&vec);
vec
}
関数名以外は記述はほぼ同じです。呼び出す関数名も変化しただけです。
このまま使っても動作はするでしょうが機能追加の度にコードが大きくなっていきそうです。
to_lines()に変更を加えて引数に関数を受け取れるようにしてみます。テストがややこしくなるので一時的にVec<Line>ではなくVec<String>を返すようにしています。
// convert_to_ascii
// 型制約を使う
pub(crate) fn to_lines5(buf: &[u8], len: usize, func: fn(&[u8]) -> String) -> Vec<String> {
let mut vec = Vec::new();
buf.chunks(len)
.for_each(|x| vec.push(format!("{:width$} {}", " ", func(x), width = 8)));
vec
}
新たにfuncという引数を追加しました。
引数の型としてfn(&[u8]) -> Stringとしました。これでどちらの関数も同じto_lines()から呼べるようになりました。
// convert_to_ascii
#[test]
fn test_to_lines_with_hello_2() {
let bin_data = setup();
let expect = setup_to_ascii();
let res = to_lines5(bin_data, 8, to_ascii_9);
dbg!(&res);
println!("{}: {:?}", "to_ascii_9", &res);
assert_eq!(res, expect);
let expect = setup_to_lines_hex();
let res = to_lines5(bin_data, 8, to_hex);
dbg!(&res);
println!("{}: {:?}", "to_hex", &res);
assert_eq!(res, expect);
// 略
注意点
ただし、これには問題があるかもしれません。
fn(&[u8]) -> Stringを満たす適当なクロージャを引数として渡しても動作します。
例えば、このよう形でも動作します。
// convert_to_ascii
let res_1 = to_lines5(bin_data, 8, |_| -> String { "hello".to_string() }); // 外部変数をキャプチャしないと関数にキャストされる
/*
let res_1 = to_lines5(bin_data, 8, |_| -> String {bin_data;"hello".to_string()}); // 外部変数をキャプチャするとクロージャとして扱われて型がマッチせずエラー
*/
dbg!(&res_1);
println!("{}: {:?}", "hello", &res_1);
クロージャを渡せるので手軽にコードを書ける柔軟性がありますが、変数をキャプチャしてしまうとエラーとなり、何が原因となっているのか分かりづらくなります。
Rustの自動変換が働いているのですが、初見殺しなコードになる可能性があります。
TOC
GitHubにコードをアップロードしています。
コードのコメントに書かれているfirst_stepなどをcargoコマンドに渡すと実行できます。
# Example
$ cargo run --examples first_step