SlideShare a Scribd company logo
1 of 77
Download to read offline
幾個Ruststd內可能的⼩技巧
byKK
2019/09/28
1
3
4
12
21
22
25
29
37
38
1. Title
2. Option
3. Option::map
4. Option::and_then
5. Result
6. Result::map
7. Result::and_then
8. Result::map_err
9. Iterator
10. Iterator::fold
Option<T>
可有可無
map
你可以這樣做
fnfn maybe_lenmaybe_len((ss:: OptionOption<<&&strstr>>)) ->-> OptionOption<<usizeusize>> {{
matchmatch ss {{
SomeSome((ss)) =>=> SomeSome((ss..lenlen(()))),,
NoneNone =>=> NoneNone,,
}}
}}
或者...
其實⼤家都在這麼做?
map
fnfn maybe_lenmaybe_len((ss:: OptionOption<<&&strstr>>)) ->-> OptionOption<<usizeusize>> {{
ss..mapmap((strstr::::lenlen))
}}
map的概念是我有
⼀個&str
⼀個fn(&str)->usize(str::len)
⼀個Option<&str>
然後我想要
fn(Option<&str>)->Option<usize>
fn(&str)->usize
mapfn(Option<&str>)->Option<usize>
現在你知道為什麼map叫做map了map把原本對&str操作的函數
變成Option版本了
為什麼要⽤map?因為這樣可以減少許多重複的操作
and_then
你可以這樣做
fnfn parse_to_i32parse_to_i32((ss:: &&strstr)) ->-> OptionOption<<i32i32>>
fnfn convert_to_u32convert_to_u32((ii:: i32i32)) ->-> OptionOption<<u32u32>>
fnfn i_dont_know_whati_dont_know_what((uu:: u32u32)) ->-> OptionOption<<StringString>>
fnfn homework_xhomework_x((ss:: &&strstr)) ->-> OptionOption<<StringString>> {{
letlet numbernumber == parse_to_i32parse_to_i32((ss));;
ifif numbernumber..is_noneis_none(()) {{ returnreturn NoneNone;; }}
letlet unsignedunsigned == convert_to_u32convert_to_u32((numbernumber..unwrapunwrap(())));;
ifif unsignedunsigned..is_noneis_none(()) {{ returnreturn NoneNone;; }}
i_dont_know_whati_dont_know_what((unsignedunsigned..unwrapunwrap(())))
}}
錯...錯棚?
and_then
fnfn homework_x_homework_x_((ss:: &&strstr)) ->-> OptionOption<<StringString>> {{
parse_to_i32parse_to_i32((ss))
..and_thenand_then((convert_to_u32convert_to_u32))
..and_thenand_then((i_dont_know_whati_dont_know_what))
}}
map是把fn(T)->U
跟
Option<T>
變出
Option<U>
and_then是把fn(T)->
Option<U>
跟
Option<T>
變出
Option<U>
為什麼要⽤and_then?and_then的概念,在於能夠串接數個結果為Option的操作
利⽤這個特性,我們能夠在Option這個上下⽂內,作出類似錯誤處理
的功能
這些性質就是傳說中的Mona...沒事
看看這個?
基本是類似的概念,只是使⽤語法糖包起來,實作也有些微不同
不過就看狀況擇⼀使⽤吧
fnfn homework_x_homework_x_((ss:: &&strstr)) ->-> OptionOption<<StringString>> {{
letlet ii == parse_to_i32parse_to_i32((ss))??;;
letlet uu == convert_to_u32convert_to_u32((ii))??;;
i_dont_know_whati_dont_know_what((uu))
}}
也是可以寫成這樣啦...
fnfn homework_xhomework_x((ss:: &&strstr)) ->-> OptionOption<<StringString>> {{
i_dont_know_whati_dont_know_what((convert_to_u32convert_to_u32((parse_to_i32parse_to_i32((ss))??))??))
}}
Result<T,E>
可對可錯
map
跟Option<T>::map⼀模⼀樣
如果想要下⾯這樣的邏輯時...
fnfn result_lenresult_len((ss:: ResultResult<<&&strstr,, i32i32>>)) ->-> ResultResult<<usizeusize,, i32i32>> {{
matchmatch ss {{
OkOk((ss)) =>=> OkOk((ss..lenlen(()))),,
ErrErr((ee)) =>=> ErrErr((ee)),,
}}
}}
你可以考慮使⽤Result<T,E>::map來取代
fnfn result_len_result_len_((ss:: ResultResult<<&&strstr,, i32i32>>)) ->-> ResultResult<<usizeusize,, i32i32>> {{
ss..mapmap((strstr::::lenlen))
}}
and_then
跟Optional<T>::and_then⼀模⼀樣
如果想要下⾯這樣的邏輯時...
fnfn parse_to_i32_rparse_to_i32_r((ss:: &&strstr)) ->-> ResultResult<<i32i32,, i32i32>>
fnfn convert_to_u32_rconvert_to_u32_r((ii:: i32i32)) ->-> ResultResult<<u32u32,, i32i32>>
fnfn i_dont_know_what_ri_dont_know_what_r((uu:: u32u32)) ->-> ResultResult<<StringString,, i32i32>>
fnfn homework_x_rhomework_x_r((ss:: &&strstr)) ->-> ResultResult<<StringString,, i32i32>> {{
letlet numbernumber == parse_to_i32_rparse_to_i32_r((ss));;
ifif letlet ErrErr((ee)) == numbernumber {{ returnreturn ErrErr((ee));; }}
letlet unsignedunsigned == convert_to_u32_rconvert_to_u32_r((numbernumber..unwrapunwrap(())));;
ifif letlet ErrErr((ee)) == unsignedunsigned {{ returnreturn ErrErr((ee));; }}
i_dont_know_what_ri_dont_know_what_r((unsignedunsigned..unwrapunwrap(())))
}}
你值得擁有更好的
fnfn homework_x_rhomework_x_r((ss:: &&strstr)) ->-> ResultResult<<StringString,, i32i32>> {{
parse_to_i32_rparse_to_i32_r((ss))
..and_thenand_then((convert_to_u32_rconvert_to_u32_r))
..and_thenand_then((i_dont_know_what_ri_dont_know_what_r))
}}
或是這樣也Ok(沒問題)
fnfn homework_x_rhomework_x_r((ss:: &&strstr)) ->-> ResultResult<<StringString,, i32i32>> {{
letlet ii == parse_to_i32_rparse_to_i32_r((ss))??;;
letlet uu == convert_to_u32_rconvert_to_u32_r((ii))??;;
i_dont_know_what_ri_dont_know_what_r((uu))
}}
這個就有待商榷了
fnfn homework_x_rhomework_x_r((ss:: &&strstr)) ->-> ResultResult<<StringString,, i32i32>> {{
i_dont_know_what_ri_dont_know_what_r((convert_to_u32_rconvert_to_u32_r((parse_to_i32_rparse_to_i32_r((ss))??))??))
}}
map_err
你可以這樣做
fnfn homework_yhomework_y((ss:: &&strstr)) ->-> ResultResult<<StringString,, StringString>> {{
letlet err_to_stringerr_to_string == ||ee|| format!format!(("got error: {}""got error: {}",, ee));;
letlet numbernumber == parse_to_i32_rparse_to_i32_r((ss));;
ifif letlet ErrErr((ee)) == numbernumber {{ returnreturn ErrErr((err_to_stringerr_to_string((ee))));; }}
letlet unsignedunsigned == convert_to_u32_rconvert_to_u32_r((numbernumber..unwrapunwrap(())));;
ifif letlet ErrErr((ee)) == unsignedunsigned {{ returnreturn ErrErr((err_to_stringerr_to_string((ee))));; }}
letlet whatwhat == i_dont_know_what_ri_dont_know_what_r((unsignedunsigned..unwrapunwrap(())));;
ifif letlet ErrErr((ee)) == whatwhat {{ returnreturn ErrErr((err_to_stringerr_to_string((ee))));; }}
OkOk((whatwhat..unwrapunwrap(())))
}}
問題來⾃於我們計算出來的是Result<T,E>
但是函數想要的回傳值是Result<T,X>
我們需要⼀個好⽅法,把Result<T,E>變成Result<T,X>!
map_err
fnfn homework_yhomework_y((ss:: &&strstr)) ->-> ResultResult<<StringString,, StringString>> {{
letlet to_strto_str == ||ee|| format!format!(("got error: {}""got error: {}",, ee));;
letlet numbernumber == parse_to_i32_rparse_to_i32_r((ss))..map_errmap_err((to_strto_str));;
ifif letlet ErrErr((ee)) == numbernumber {{ returnreturn ErrErr((ee));; }}
letlet unsignedunsigned == convert_to_u32_rconvert_to_u32_r((numbernumber..unwrapunwrap(())))..map_errmap_err((to_strto_str));;
ifif letlet ErrErr((ee)) == unsignedunsigned {{ returnreturn ErrErr((ee));; }}
letlet whatwhat == i_dont_know_what_ri_dont_know_what_r((unsignedunsigned..unwrapunwrap(())))..map_errmap_err((to_strto_str));;
ifif letlet ErrErr((ee)) == whatwhat {{ returnreturn ErrErr((ee));; }}
OkOk((whatwhat..unwrapunwrap(())))
}}
嗯...事情看起來還是挺糟糕的
⽤上之前學過的好⽤技巧後...
fnfn homework_yhomework_y((ss:: &&strstr)) ->-> ResultResult<<StringString,, StringString>> {{
parse_to_i32_rparse_to_i32_r((ss))
..and_thenand_then((convert_to_u32_rconvert_to_u32_r))
..and_thenand_then((i_dont_know_what_ri_dont_know_what_r))
..map_errmap_err((||ee|| format!format!(("got error: {}""got error: {}",, ee))))
}}
沒錯,只要多加⼀⾏map_err,就能好好的表達意圖了
使⽤?的版本,看起來效果有限
fnfn homework_y_homework_y_((ss:: &&strstr)) ->-> ResultResult<<StringString,, StringString>> {{
letlet err_to_strerr_to_str == ||ee|| format!format!(("got error: {}""got error: {}",, ee));;
letlet ii == parse_to_i32_rparse_to_i32_r((ss))..map_errmap_err((err_to_strerr_to_str))??;;
letlet uu == convert_to_u32_rconvert_to_u32_r((ii))..map_errmap_err((err_to_strerr_to_str))??;;
i_dont_know_what_ri_dont_know_what_r((uu))..map_errmap_err((err_to_strerr_to_str))
}}
為什麼要⽤map_err?因為這樣可以減少許多重複的操作
Iterator
⼀個⼀個來
fold
你⼀定這樣做過
fnfn sumsum((numsnums:: &&[[i32i32]])) ->-> i32i32 {{
letlet mutmut sumsum == 00;;
forfor nn inin numsnums {{
sumsum +=+= nn;;
}}
sumsum
}}
但是我們可以做的更好
先讓我們忽略這個
fnfn sumsum((numsnums:: &&[[i32i32]])) ->-> i32i32 {{
numsnums..iteriter(())..sumsum(())
}}
fold的概念是什麼?我們有⼀連串的資料,經過⼀些操作,產⽣⼀個資料
⽤剛剛的sum當作例⼦,如果使⽤fold來寫,是這樣
numsnums..iteriter(())..foldfold((00,, i32i32::::addadd))
或是
numsnums..iteriter(())..foldfold((00,, ||accacc,, xx|| accacc ++ xx))
numsnums..iteriter(())..foldfold((00,, ||accacc,, xx|| accacc ++ xx))
⼀連串的資料=很多i
⼀些操作=加法
最後的結果=⼀個i
fold真的不複雜
letlet numsnums == [[11,,22,,33,,44,,55]];;
letlet sumsum == numsnums..iteriter(())..foldfold((00,, ||accacc,, xx|| accacc ++ xx));;
其實就是
letlet numsnums == [[11,,22,,33,,44,,55]];;
letlet sumsum == ((((((((((00 ++ 11)) ++ 22)) ++ 33)) ++44)) ++ 55))
所以我們才需要給⼀個起始值0
為什麼要⽤fold?因為這樣可以減少許多重複的操作
繼續使⽤剛剛的例⼦,我們已經有了
i 這個型別
add(+)這個函數
⼀連串的i
要完成sum,我們缺少的就是⼀個把他們組合起來的明確概念了
當然for配合上⼀個mut變數可以做到⼀樣的事情
但是for並不算是⼀個明確概念,它很模糊,你甚⾄可以中途return
===
明確的概念=溝通上的精確=減少錯誤產⽣的機會
重點是!
出錯的話就是fold的實作者的問題了,不是我XD
flat_map
先來說說Iterator::map就好
letlet numsnums == [[11,,22,,33]];;
numsnums..iteriter(())..mapmap((||ee|| ee ** ee));;
// 我們可以拿到 [1, 4, 9] 的 Iterator// 我們可以拿到 [1, 4, 9] 的 Iterator
[1,2,3]經過運算|e|e*e變成[1,4,9],數量是不變的
如果我們想要這樣做呢
[[11,,22,,33]] ->-> [[11,, 22,, 22,, 33,, 33,, 33]]
對於每個元素n,產⽣n個n
很明顯,這個並不map的⾏為,因為數量改變了
過去我們可能會這樣做
letlet mutmut xx == VecVec::::newnew(());;
forfor ii inin 11..=..=55 {{
forfor __ inin 00....ii {{
xx..pushpush((ii));;
}}
}}
這是個可⾏的解法,但是有個很嚴重的缺點
那就是得到的結果並不是Iterator,這表⽰所有東⻄都要乖乖算完才能
得到結果
我們到底想要做什麼呢
[[11,,22,,33]]
=>=> [[[[11]],, [[22,, 22]],, [[33,, 33,, 33]]]] // A// A
=>=> [[11,, 22,, 22,, 33,, 33,, 33]] // B// B
A的部分,數量是⼀樣的,這表⽰我們可以⽤map,從i 產⽣
Iterator<i >
B的部分,則是要把Iterator<Iterator<i >>變成Iterator<i >
map⼤家都很熟了,就不多說
我們可以⽤iter::repeat跟take達到⽬的
fnfn repeat_nrepeat_n((nn:: u16u16)) ->-> implimpl IteratorIterator<<ItemItem == u16u16>> {{
repeatrepeat((nn))..taketake((usizeusize::::fromfrom((nn))))
}}
[[11,, 22,, 33]]..iteriter(())..mapmap((repeat_nrepeat_n))
// [[1], [2, 2], [3, 3, 3]]// [[1], [2, 2], [3, 3, 3]]
這樣我們就拿到Iterator的Iterator了
B的部分,則是要把Iterator<Iterator<i >>變成Iterator<i >
太好了,Iterator::flatten就是這樣⽤的!
fnfn repeat_nrepeat_n((nn:: u16u16)) ->-> implimpl IteratorIterator<<ItemItem == u16u16>> {{
repeatrepeat((nn))..taketake((usizeusize::::fromfrom((nn))))
}}
[[11,, 22,, 33]]..iteriter(())..mapmap((repeat_nrepeat_n))
// [[1], [2, 2], [3, 3, 3]]// [[1], [2, 2], [3, 3, 3]]
[[11,, 22,, 33]]..iteriter(())..mapmap((repeat_nrepeat_n))..flattenflatten(())
// [1, 2, 2, 3, 3, 3]// [1, 2, 2, 3, 3, 3]
現在你知道為什麼他叫做flat_map了!
他是計算完後,會進⾏flatten的map
[[11,, 22,, 33]]..iteriter(())..flat_mapflat_map((repeat_nrepeat_n))
// [1, 2, 2, 3, 3, 3]// [1, 2, 2, 3, 3, 3]
[[11,, 22,, 33]]..iteriter(())..mapmap((repeat_nrepeat_n))
// [[1], [2, 2], [3, 3, 3]]// [[1], [2, 2], [3, 3, 3]]
[[11,, 22,, 33]]..iteriter(())..mapmap((repeat_nrepeat_n))..flattenflatten(())
// [1, 2, 2, 3, 3, 3]// [1, 2, 2, 3, 3, 3]
為什麼要⽤flat_map?因為這樣可以減少許多重複的操作
等等...
怪怪的
fnfn flat_mapflat_map((IterIter<<TT>>,, fnfn((TT)) ->-> IterIter<<UU>>)) ->-> IterIter<<UU>>
fnfn and_thenand_then((OptionOption<<TT>>,, fnfn((TT)) ->-> OptionOption<<UU>>)) ->-> OptionOption<<UU>>
fnfn and_thenand_then((ResultResult<<TT,, EE>>,, fnfn((TT)) ->-> OptionOption<<UU,, EE>>)) ->-> OptionOption<<UU,, EE>>
FromIterator
通通組合起來
FromIterator說起來可能有些陌⽣,但是他最常⽤的地⽅就是這個
((11....100100))..collectcollect::::<<VecVec<<__>>>>(())
就是那個collect
這表⽰Vec<T>有實作FromIterator<T>
因為概念都⼀樣,後⾯就快速看看⽤法吧
implFromIterator<(K,V)>forHashMap<K,V>
letlet mapmap:: HashMapHashMap<<StringString,, i32i32>> == [[
(("one""one"..intointo(()),, 11)),,
(("two""two"..intointo(()),, 22)),,
(("the_answer""the_answer"..intointo(()),, 4242)),,
]]
..iteriter(())
..clonedcloned(()) // 要記得這個,型別才會對// 要記得這個,型別才會對
..collectcollect(());;
// HashMap 內容有// HashMap 內容有
// ("one", 1),// ("one", 1),
// ("two", 2)// ("two", 2)
// ("the_answer", 42)// ("the_answer", 42)
implFromIterator<String>forString
letlet ss:: StringString == [[
"apple > ""apple > ",,
"orange > ""orange > ",,
"lemon > ""lemon > ",,
]]
..iteriter(())
..clonedcloned(()) // 要記得這個,型別才會對// 要記得這個,型別才會對
..mapmap((strstr::::to_stringto_string))
..collectcollect(());;
// "apple > orange > lemon > "// "apple > orange > lemon > "
implFromIterator<Option<A>>forOption<V>
whereV:FromIterator<A>
letlet oo:: OptionOption<<VecVec<<i32i32>>>> == [[
SomeSome((1111)),,
SomeSome((1212)),,
SomeSome((2424)),,
]]
..iteriter(())
..clonedcloned(()) // 要記得這個,型別才會對// 要記得這個,型別才會對
..collectcollect(());;
// Some([11, 12, 24])// Some([11, 12, 24])
implFromIterator<Option<A>>forOption<V>
whereV:FromIterator<A>
letlet oo:: OptionOption<<VecVec<<i32i32>>>> == [[
SomeSome((1111)),,
NoneNone,,
SomeSome((2424)),,
]]
..iteriter(())
..clonedcloned(()) // 要記得這個,型別才會對// 要記得這個,型別才會對
..collectcollect(());;
// None// None
implFromIterator<Result<A,E>>forResult<V,E>
whereV:FromIterator<A>
letlet rr:: ResultResult<<VecVec<<i32i32>>,, StringString>> == [[
OkOk((1111)),,
OkOk((2222)),,
OkOk((2424)),,
]]
..iteriter(())
..clonedcloned(()) // 要記得這個,型別才會對// 要記得這個,型別才會對
..collectcollect(());;
// Ok([11, 22, 24])// Ok([11, 22, 24])
implFromIterator<Result<A,E>>forResult<V,E>
whereV:FromIterator<A>
letlet rr:: ResultResult<<VecVec<<i32i32>>,, StringString>> == [[
OkOk((1111)),,
ErrErr(("Error!!""Error!!"..to_stringto_string(()))),,
OkOk((2424)),,
]]
..iteriter(())
..clonedcloned(()) // 要記得這個,型別才會對// 要記得這個,型別才會對
..collectcollect(());;
// Err("Error!!")// Err("Error!!")
為什麼要⽤FromIterator?因為這樣可以減少許多重複的操作
藉由從Iterator產⽣需要的資料結構,我們就獲得了使⽤Iterator的能
⼒例如map,filter,take_while等等
不⽌好懂,也更好維護
hash_map::Entry
不只是Option
你可能會這樣寫
letlet mutmut mm == HashMapHashMap::::<<i32i32,, StringString>>::::newnew(());;
ifif letlet NoneNone == mm..getget((&&4242)) {{
mm..insertinsert((4242,, "the answer""the answer"..intointo(())));;
}}
letlet valuevalue == mm..getget((&&4242))..unwrapunwrap(());;
Entry
letlet mutmut mm == HashMapHashMap::::<<i32i32,, StringString>>::::newnew(());;
letlet valuevalue == mm..entryentry((4242))..or_insert_withor_insert_with((|||| "the answer""the answer"..intointo(())));;
這個api在collections裡⾯的容器幾乎都有實作
為什麼要⽤FromIterator?因為這樣可以減少許多重複的操作
⽽且少掉了unwrap的危險操作
Q&A
Thanks!

More Related Content

Similar to 幾個 Rust std 內可能有用的小技巧

Similar to 幾個 Rust std 內可能有用的小技巧 (9)

Ppt 1-50
Ppt 1-50Ppt 1-50
Ppt 1-50
 
ncuma_邏輯與迴圈.pptx
ncuma_邏輯與迴圈.pptxncuma_邏輯與迴圈.pptx
ncuma_邏輯與迴圈.pptx
 
Ppt 26-50
Ppt 26-50Ppt 26-50
Ppt 26-50
 
Ch5 範例
Ch5 範例Ch5 範例
Ch5 範例
 
Ch2 教學
Ch2 教學Ch2 教學
Ch2 教學
 
Ch8
Ch8Ch8
Ch8
 
Ch8 教學
Ch8 教學Ch8 教學
Ch8 教學
 
Maintainable Javascript
Maintainable JavascriptMaintainable Javascript
Maintainable Javascript
 
Python入門:5大概念初心者必備
Python入門:5大概念初心者必備Python入門:5大概念初心者必備
Python入門:5大概念初心者必備
 

幾個 Rust std 內可能有用的小技巧