Rust’ın en incelikli sözdizimi
(zkrising.com)Rust’ta let ve const
let, yeni bir değişken tanımlamak için kullanılırlet PAT = EXPR;biçimindedir ve göründüğünden daha güçlüdür- Pattern matching ile birleşerek kullanışlı özellikler sunar
let (a, b) = (5, 10);let maybe_string: Option<String> = ..;let Some(value) = maybe_string else { panic!("die horribly")};
const, derleme zamanında hesaplanıp derlenmiş koda doğrudan gömülen sabittirconst MY_VAR: &str = "heyyyyyyyy man";const SECRET: i32 = 0x1234;const IDENT: TYPE = EXPR;biçimindedir; tür açıkça belirtilmelidir ve pattern kullanılamaz
Kafa karıştıran kısım
const, tanımlama sırasından bağımsız olarak kullanılabilir (hoisting)
// X, Y'den sonra tanımlansa bile derlenir
const Y: i32 = X + X;
const X: i32 = 5;
- Fonksiyon içinde de tanımlanabilir ve orada da hoisting geçerlidir
fn oh_boy() -> i32 {
return X;
const X: i32 = 5;
// ^ Derlenir ve çalışır. Uyarı da yok!
}
- JavaScript geçmişinden gelip Rust öğrenmeye yeni başlayan bir programcıyla çalışıyorsanız, bu özellik onları afallatmak için harikadır
- Güzel bir özelliğin zararsız sonucu bu; şimdi zararlı sonuçlara bakalım
Rust’ta Match
// let PAT = EXPR;
let x = 5;
// Bu durumda `x` bir patterndir. `5` değerinin `x`e konup konamayacağını kontrol ederiz
// Bu pattern her zaman eşleşir -- çünkü `x` adlı değişkene her zaman 5 atanabilir
// Her pattern mutlaka eşleşmek zorunda değildir. Örneğin:
let (5, x) = (a, b);
// Burada ifade, yalnızca a == 5 ise pattern ile "eşleşir"
//
// Buna "refutable" pattern denir
//
// `let` bildiriminde, refutable pattern'lerde "reddedilen" durumu ele almak gerekir:
let (5, x) = (a, b) else { panic!() };
//
// ...aksi halde "koşullu olarak var olan" değişkenleriniz olabilir; bu da iyi değildir
- O halde
matche bakalım.matchnedir?
// match, pattern'lerle eşleştiğinde yapılacak işlemlerin listesidir
//
// match EXPR {
// PAT => EXPR
// PAT => EXPR
// ..
// }
match (a, b) {
(5, x) => {
// Eğer (a,b), (5,x) ile eşleşirse bu blok çalışır
},
(x, 5) => {
// Aynı şekilde: eğer (a,b), (x, 5) ile eşleşirse..
},
(x, y) => {
// Bu da "her şeyi yakalayan" pattern'dir; let (x,y) = (a,b) ile aynı şekilde çalışır
}
}
Biraz acı verelim
- İnsanların kafasını karıştırmak eğlenceli ama ya tam anlamıyla mutsuzluk ve gerçek bug'lar üretmek?
- Bana göre bu, Rust’ın en incelikli sözdizimi:
- Bu yazıdaki en ilginç satır şu: Rust’ın en incelikli sözdizimi, sabitlerin kendisinin pattern olmasıdır
- Bu sözdizimi, matching etrafında bazı güzel ergonomiler sağlar:
let input: i32 = ..;
const GOOD: i32 = 1;
const BAD: i32 = 2;
match input {
// Bu, input == GOOD olup olmadığını kontrol eder. Çünkü GOOD bir sabittir
GOOD => println!("input was 1"),
// Bu, input == BAD olup olmadığını kontrol eder. Çünkü BAD bir sabittir.
BAD => println!("input was 2"),
// Bu ise otherwise = input olarak tanımlar ve her zaman eşleşir...
otherwise => println!("input was {otherwise}"),
}
Ama sabitleri büyük harfle yazmak sadece bir konvansiyondur. Böyle yapmadığınızda yalnızca derleyici uyarısı alırsınız.
const good: i32 = 1;
const bad: i32 = 2;
match input {
// Hmm...
good => {},
bad => {},
otherwise => {},
}
Artık elimizde aynı görünen üç dal var ama yaptıkları şey, o isimde bir sabitin var olup olmamasına göre değişiyor!
Daha da kötüleştirelim. Aşağıda ne olur?
const GOOD: i32 = 1;
match input {
// Yazım hatası...
GOD => println!("input was 1"),
otherwise => println!("input was not 1")
}
Burada derleyici uyarı verir ama bu kod her zaman input was 1 yazdırır
Ya da daha gerçekçi bir örnek:
// Tüh, bu import'u yanlışlıkla yorum satırına almış veya silmişiz
// use crate::{SOME_GL_CONSTANT, OTHER_THING}
// Eyvah!
match value {
SOME_GL_CONSTANT => ..,
OTHER_THING => ..,
_ => ..,
}
Bu, insanların kafasını karıştırır. Özellikle enum'larla havalı şeyler yapmaya çalıştıklarında daha da fazla.
enum MyEnum {
A, B, C
}
// Normalde şöyle yazarsınız
match value {
MyEnum::A => ..,
MyEnum::B => ..,
MyEnum::C => ..,
}
// Ama şöyle de yazabilirsiniz
use MyEnum::*;
match value {
A => {},
B => {},
C => {}
}
// Sonra diyelim ki MyEnum'u değiştirdiniz...
enum MyEnum { A, B, D, E };
use MyEnum::*;
// Bu hâlâ derlenir!
match value {
A => {},
B => {},
C => {},
}
// `C` artık "her şeyi yakalayan" bir pattern olur. Çünkü kapsamda `C` diye bir şey yoktur.
// Yani aslında let C = value yapmış olursunuz ve bu her zaman eşleşir!!!
Clippy, bunu yapmamanız için pek çok kurala sahip; çünkü bu durum insanları sürekli şaşırtır.
Ama daha da kafa karıştırıcı hâle gelebilir:
// x'i 5'e irrefutably bağla...
let x = 5;
// ...bir dakika...
const x: i32 = 4;
Bu kod derlenmez. Çünkü const x bir pattern'dir, sabitler hoist edilir ve bu kod artık şu şekilde değerlendirilir:
let 4 = 5;
// error[E0005]: local binding içinde refutable pattern
// --> src/main.rs:3:5
// |
// 3 | let x = 5;
// | ^
// | |
// | `i32::MIN..=3_i32` ve `5_i32..=i32::MAX` pattern'leri kapsanmıyor
// | eksik pattern'ler kapsanmıyor çünkü `x`, yeni bir değişken değil sabit pattern olarak yorumlanıyor
// | yardım: bunun yerine bir değişken tanıtın: `x_var`
// |
// = not: `let` binding'leri "irrefutable pattern" gerektirir; örneğin `struct` ya da tek varyantlı `enum` gibi
"expr 4'e eşittir" ifadesi refutable olmayan bir eşleşme değildir ve başka bir durumu da ele almaz
Etrafınızdaki herkesi sinirlendirmek
// `maybe`'in Option<&str> olduğunu varsayalım. Bir metin içerebilir ya da None olabilir.
let maybe_username: Option<&str> = ..;
// Bu, tek satırlık match'te Rust'ta yaygın bir pattern. Eğer Some(..) ile eşleşirse bu string ile bir şey yapabiliriz.
if let Some(username) = maybe_username {
// Yani bu kod username varsa çalışır...
return username.to_uppercase();
}
// Ama işte... artık bu kod yalnızca 'username', Some("hey") ile eşleştiğinde çalışır
const username: &str = "hey";
Sabitlerin hoist edilmesi ile sabitlerin pattern olması birleşince, bilmece gibi Rust kodları yazabilirsiniz
Bu aslında gerçek bir sorun değil
- Gerçekte bunun kafa karıştırıcı olmasının tek nedeni,
let UPPERCASEveconst lowercaseyazabilmenizdir - Eğer büyük harfle başlayan değişken oluşturmak lint hatası olsaydı, bu karışıklık yaşanmazdı
- Çünkü enum variant'ı ya da sabitle eşleştirmeye çalışırken yanlışlıkla bir şeyi bind edemezdiniz
- Ama net olalım: bu sadece dilin eğlenceli bir tuhaflığı
macro_rules! f {
($cond: expr) => {
if let Some(x) = $cond {
println!("i am some == {x}!");
} else {
println!("i am none");
}
}
}
fn main() {
f!(Some(100));
{
f!(Some(100));
return;
const x: i32 = 5;
}
}
3 yorum
Aslında büyük bir sorun değil; çünkü çoğu geliştirme ortamında bir language server var ve
orada hepsini çıkarım yapıp gösteriyor.
RustRover'ın language server altyapısının temeli olan rust-analyzer oldukça güçlü bir araç.
Sadece herhangi bir dilde bulunan dark pattern'ları bir araya getirip
Bu, kafa karışıklığına yol açabilir!
Böyle bir hissiyat veren bir yazı işte
Vay canına... düşündürücü. Rust bunu nasıl ele almayı planlıyor acaba?