Go不能寫,但是可以用Rust寫的三段代碼!
在這篇文章中,要分享的例子不僅僅是假設,它們來自于工作中的真實案例,在這些例子中,Go的局限性無法實現(xiàn)所需的解決方案。聲明:這里的區(qū)別并不在于Rust代碼比Go代碼更正確或更快。
1.讀取線程的ID
記錄當前線程的ID,或者在Go的情況下,記錄協(xié)程ID,是非常有用的。它明確了哪個線程正在做什么。如果沒有這些信息,每個線程的活動就會交織在一個日志文件中,因此很難跟蹤單個執(zhí)行流。
在Rust中,獲取線程id就像這樣簡單:
let id = thread::current().id();
然而,Go并不公開協(xié)程id。Go故意不公開協(xié)程id,以阻止開發(fā)人員對線程本地存儲進行編程。對于想要理解日志的開發(fā)人員必須求助于其他方法來檢索該信息。
2.單個Select語句中的Case優(yōu)先級排序
Go在select-case語句(等待多個通道操作)中隨機選擇所有就緒中的一個。如果準備好了多個case,并且希望在單個select語句中優(yōu)先執(zhí)行一個case,則不行。
在下面的代碼中,nReady1在統(tǒng)計上等于nReady2。
func main() {
ready1 := make(chan struct{})
close(ready1)
ready2 := make(chan struct{})
close(ready2)
nReady1 := 0
nReady2 := 0
N := 10_000
for i := 0; i < N; i++ {
select {
case <-ready1:
nReady1++
case <-ready2:
nReady2++
}
}
fmt.Println("nReady1:", nReady1)
fmt.Println("nReady2:", nReady2)
}
執(zhí)行結果:
nReady1: 4943
nReady2: 5057
必須使用嵌套的select語句(帶有默認值)來實現(xiàn)優(yōu)先級。
select {
case <-ready1:
nReady1++
default:
select {
case <-ready1:
nReady1++
case <-ready2:
nReady2++
}
}
然而,Rust的異步運行時Tokio允許在單個select語句中使用biased關鍵字設置優(yōu)先級順序。
在下面的例子中,按照它們在代碼中出現(xiàn)的順序排列優(yōu)先級。
use tokio::sync::mpsc;
use tokio::select;
#[tokio::main]
async fn main() {
let (tx1, mut rx1) = mpsc::channel::<()>(1);
drop(tx1);
let (tx2, mut rx2) = mpsc::channel::<()>(1);
drop(tx2);
let mut n_ready1 = 0;
let mut n_ready2 = 0;
let n = 10_000;
for _ in 0..n {
select! {
biased; // 按出現(xiàn)的順序優(yōu)先處理已準備好的case
_ = rx1.recv() => {
n_ready1 += 1;
},
_ = rx2.recv() => {
n_ready2 += 1;
},
}
}
println!("n_ready1: {}", n_ready1);
println!("n_ready2: {}", n_ready2);
}
執(zhí)行結果:
n_ready1: 10000
n_ready2: 0
Rust使用單個select語句實現(xiàn)了對case的優(yōu)先級排序。
3.具有指針和值接收器的泛型類型
在Go中,你想為類型參數(shù)S定義一個類型約束,它實現(xiàn)了兩個方法:
type S struct{}
func (s S) M() {}
func (s *S) P() {}
不幸的是,不可能用單個類型約束指定這兩個方法。必須使用兩個單獨的類型參數(shù),每個類型參數(shù)都有自己的約束,然后將它們鏈接到函數(shù)f中。首先,定義T受Mer接口類型的約束。然后,將PT定義為受Per[T]接口類型約束,并引用第一個T。這看起來很復雜,并不直觀。
type Per[T any] interface {
P()
*T // 非接口類型約束元素
}
type Mer interface {
M()
}
func f[T Mer, PT Per[T]](t T) {
PT(&t).P()
t.M()
}
在Rust中,解決方案很簡單。定義一個單獨的trait:MyTrait,然后將它用作f中的一個trait綁定。
trait MyTrait {
fn M(&self);
fn P(&mut self);
}
struct S;
impl MyTrait for S {
fn M(&self) {}
fn P(&mut self) {}
}
fn f<T: MyTrait>(t: &mut T) {
t.M();
t.P();
}
總結
Rust通過在編譯時而不是運行時檢測錯誤來確??煽啃?,實現(xiàn)高性能,并且具有一定程度的表達性,使其可以編寫其他語言無法編寫的代碼。