14 puan yazan xguru 2024-11-05 | 3 yorum | WhatsApp'ta paylaş

Rust’ta let ve const

  • let, yeni bir değişken tanımlamak için kullanılır
    • let 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 sabittir
    • const 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. match nedir?
// 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 UPPERCASE ve const lowercase yazabilmenizdir
  • 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

 
sunrabbit 2024-11-05

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ç.

 
sunrabbit 2024-11-05

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

 
kayws426 2024-11-05

Vay canına... düşündürücü. Rust bunu nasıl ele almayı planlıyor acaba?