Google Style Guides

Rust Stil Rehberi

Bu belge, Rust programlama dilinin Google'daki kullanımına yönelik bir stil rehberidir. Bu bağlamda "stil", yalnızca biçimlendirmeyi değil, API ve uygulama tasarımına ilişkin yönergeleri de kapsar.

Rust; google3, Android, ChromeOS, Fuchsia ve Chrome dâhil olmak üzere birçok yerde kullanılıyor ve Google'daki Rust kullanımının hem kod tabanları içinde hem de kod tabanları arasında tutarlı olmasını istiyoruz. Stil yönergeleri, Rust kodumuza tutarlılık kazandırmanın yanı sıra bir kalite standardı oluşturmaya da yardımcı olur.

Arka Plan

Kapsam

Bu rehberdeki kuralların hâlihazırdaki geçerlilik kapsamı:

Stil rehberinin amaçları

Genel olarak bu belgeyi C++ stil rehberine sahip olmamızla aynı nedenlerle tutuyoruz.

Stil kurallarının çoğu aşağıda sıralanan amaçlardan bir veya birkaçından doğar.

Stil kuralları, yazılabilirlikten çok okunabilirliğe ve sürdürülebilirliğe öncelik vermelidir
Kodumuzun uzun süre hayatta kalmasını bekliyoruz ve bir stil kuralı okunabilirliği artırıyorsa, kod yazarken harcanan biraz fazla çabanın uzun vadede buna değdiğini düşünüyoruz.
Stil kuralları daha iyi otomasyonu desteklemelidir
Büyük Ölçekli Değişiklikler (LSC'ler) Google'da yaygındır ve tutarlılık, LSC'lerin uygulanmasını kolaylaştırır.
Stil kuralları mümkün olduğunca geniş Rust ekosistemiyle tutarlı olmalıdır
Daha geniş Rust ekosistemi stilde hâlihazırda oldukça iyi bir tutarlılığa sahiptir; sürprizleri azaltmak ve Google'ın Rust stilini öğrenmeyi kolaylaştırmak için mümkün olduğunca buna yaslanmalıyız.
Stil kuralları ağırlığını taşımalıdır
Google'daki her Rust geliştiricisi ve inceleyicisi bu stil kurallarını akılda tutmak zorundadır; bu nedenle yalnızca somut faydası olan kuralları dâhil etmeye özen göstermeliyiz.
Stil kuralları küçük stil anlaşmazlıklarının önüne geçmeye yardımcı olabilir
Makul insanların bir stil konusunda anlaşamayabileceği pek çok durum vardır ve bu anlaşmazlığın CL'lerde sık sık ortaya çıkmasını bekliyoruz. Bir stil kuralının olması, aynı tartışmanın her seferinde yeniden yapılmasını önler. Bu kategoride olup başka faydası bulunmayan kurallar asgari düzeyde tutulmalıdır.

Herkese açık Rust stil kurallarına uyun

Bu stil rehberiyle çelişmediği ölçüde resmî, herkese açık Rust stil kurallarına uyun.

Bir "çelişki", bu stil rehberinin Rust stil rehberinde önerilmeyen veya Rust stil rehberinin öncelikli olarak teşvik ettiği kalıba aykırı bir kalıp önerdiği durumdur.

Bu stil rehberindeki yönergeler neden olağan Rust stil kurallarından sapıyor?

Stil rehberlerinin çoğunun üç amacı vardır: tutarlılık, okunabilirlik ve iyi API tasarımını teşvik etmek. Hangi seçimlerin yapıldığından bağımsız olarak, bu seçimlerin önceden yapılmış olması herkesin işini kolaylaştırır ve gereksiz tartışmaları (bikeshedding) sınırlar.

Bu stil rehberi de bu amaçları paylaşıyor; ancak sık görülmeyen ek bir amacı da var: Büyük Ölçekli Değişikliklerin (LSC'lerin) yolunu açmak. Google'daki kod tabanlarının çoğu gibi büyük kod tabanlarının zaman zaman araçlar tarafından desteklenen, kod tabanının tamamına yayılan refaktörlere ihtiyacı olur: bunun iyi çalışması için kodun, araçların kolayca işleyebileceği bir biçimde olması gerekir. Tutarlılık buna yardımcı olur; bilinçli olarak refaktöre uygun kalıplar kullanmak da öyle.

API tasarım kurallarını bağlama göre uygulayın

API tasarımıyla ilgili kuralları uygularken iki tür kodu ayırt etmelisiniz:

Kütüphane kodu için API tasarımına ilişkin stil rehberi kuralları bağlayıcıdır.

Uygulama kodu için API tasarımı kuralları güçlü bir öneridir. Bunları uygularken unutmayın: uygulama kodu belirli bir problemi çözmek için yazıldığından, geniş yeniden kullanılabilirlik yerine genellikle basitliği ve doğrudanlığı tercih etmelidir. Erken genelleştirme ve soyutlamadan kaçının; acil ihtiyacı karşılamak için gerekenlere odaklanın.

Kod organizasyonu

Terminoloji:

Aşağıdaki bölümler, Chromium'da modülleri dosyalara bölmenin tüm olanaklarından yararlanmamanın tercih edildiğini anlatır.

Küçük crate'leri tercih edin [chrome]

Chromium'da küçük crate'leri tercih edin; ideal olarak –ama zorunlu değil– tek dosya (tek bir soyutlamaya odaklanmış). Kodunuz küçük crate'lere bölünebiliyorsa bölün.

Bir crate, bir veya daha fazla .rs dosyası içerebilen tek bir rust_static_library (chrome) kuralına karşılık gelir.

Ek hususlar:

Trait impl tutarlılık kuralları birden fazla tür ve trait'in tek bir kütüphanede yer almasını zorunlu kıldığında veya büyük bir temel kütüphane oluşturuyorsanız daha büyük crate'lere izin verilir.

Gerekçe

Kod tabanımızın devasa boyutu, derleme grafiğine bağımlılık eklemenin yerel olmayan etkileri ve paralel derleme ile derleme önbelleğe alma performansını iyileştirmek nedeniyle Chromium'da ince taneli kütüphane hedefleri önerilir.

rust_library kaynakları için düz dosya düzeni kullanın [chrome]

Chromium'da tek bir rust_library'ye (tek bir Rust crate'ine) ait .rs kaynak dosyaları tek bir dizinde bulunmalıdır.

Tek bir crate'e ait dosyaları düzenlemek için alt dizinler kullanmayın. Bir crate'in dosyalarını alt dizinlere ayırmak gerekli görünüyorsa bu, crate'in çok büyüdüğünün ve bölünmesi gerektiğinin güçlü bir göstergesidir.

Bu crate düzeni, açık kaynak depolarında yaygın olarak kullanılan geleneksel Cargo dizin yapısından bilinçli olarak farklıdır.

İstisna: //third_party içinde olup aynı zamanda Cargo ile derlenen Rust kodu, geleneksel Cargo dizin yapısını izlemelidir.

Gerekçe

Chromium'daki yaklaşım şu etkenlerden kaynaklanır:

Kaynak dosyaları ve yaprak modülleri, birincil kullanıcı tanımlı öğeye göre adlandırın

Bir yaprak modülün birincil kullanıcı tanımlı öğesi olduğunda (ör. Foo), modül bu öğeye göre adlandırılmalıdır (ör. mod foo ve foo.rs).

Belirli bir soyutlamayla birincil olarak ilgili olmayan yaprak modüller istenildiği gibi adlandırılabilir.

Gerekçe

Bu, bir kaynak dosyanın veya yaprak modülün içeriğini/amacını bir bakışta tanımlamayı kolaylaştırır.

Yaprak olmayan modüllerde API uygulamayın

Yaprak olmayan modüllerdeki tek öğeler modül bildirimleri (tanımlar değil) ve yeniden dışa aktarımlar olmalıdır.

Yaprak modüllerin tanımının şu alt modüllere izin verdiğini unutmayın: mod test ve #[cxx::bridge] gibi diller arası bağlama modülleri.

İstisnalar:

Gerekçe

Bu, bir şeyleri bulmayı kolaylaştırır ve yapı ile içeriğin karışmasını önler.

Glob import kullanmayın

Glob import kullanmayın; ancak kendi takdirinize bağlı olarak isteğe bağlı şekilde kullanabileceğiniz şu durumlar hariç:

Prelude'u teşvik eden üçüncü taraf crate'ler kullanılıyorsa buna izin vermek inceleyicinin takdirine bağlıdır.

Bu ayrıca prelude tanımlamaktan kaçınılması gerektiği anlamına gelir.

Gerekçe

Glob import'lar, LSC çalıştıran kod temizlikçileri için angaryayı artırır ve xref'lerin bulunmadığı bağlamlarda bir türün nereden geldiğini bilmeyi zorlaştırır.

Bir use ifadesi tek bir modülden öğe import etmelidir

Bu kural, imports_granularity = Module rustfmt ayarına karşılık gelir.

Temel olarak, bir use ifadesinin {..} bloğunun içinde hiçbir :: bulunmamalıdır. Örneğin:

use foo::Foo;
use foo::bar::Bar;
use foo::bar::Baz;
use foo::Foo;
use foo::bar::{Bar, Baz};
use foo::{Foo, bar::Bar};

Yerel kapsamlı trait import'larını tercih edin

Trait'in metotları dosya boyunca yoğun olarak kullanılmadığı sürece trait'leri fonksiyon gövdeleri içinde import edin.

fn foo(s: &str) -> Result<u8, std::num::ParseIntError> {
    // Trait'i modül kapsamında değil, fonksiyon içinde import edin.
    use std::str::FromStr;

    u8::from_str(s)
}

Üst düzey fonksiyon çağrılarını bir modül adıyla niteleyin

Başka modüllerde veya crate'lerde tanımlanmış üst düzey fonksiyonları kullanmak için modüllerini import edin ve onları modulename::functionname() şeklinde çağırın. Örneğin:

use std::mem;

let x = mem::transmute(y);
use std::mem::transmute;

let x = transmute(y);

İstisnalar:

Gerekçe

Pek çok Rust kod tabanı, nitelenmemiş küçük harfli adların yerel kabul edildiği bir stili tutarlı biçimde korur; yani foo() fonksiyonunun aynı modülden geldiği varsayılabilir. Bu kural büyük ölçüde inceleyiciler arasındaki anlaşmazlıkları önlemek için vardır; faydası çoğunlukla tutarlılık ve kodu xref'ler olmadan okumada küçük bir avantajdır.

Tür adları import edilmeli ve nitelenmeden kullanılmalıdır

Varsayılan olarak türler ve trait'ler, kodda modül yolları kullanılmadan belirtilmelidir; örneğin kod regex::Regex değil, dosyanın başında veya gerektiğinde ilgili fonksiyonun içinde use regex::Regex ile birlikte Regex kullanmalıdır.

İstisnalar:

Gerekçe

Bu kural büyük ölçüde inceleyiciler arasındaki anlaşmazlıkları önlemek için vardır.

Uygun import mekanizmasını kullanın [chrome]

Kod biçimlendirme ve estetik

Uygun biçimlendirme aracını kullanın

Uygun biçimlendirme aracını kullanın (ör. Chromium'da git cl format).

Yapılandırma (stil rehberi yazarları için)

Bir kod tabanı, kararlı (stable) Rust üzerinde derlemeyi önemsiyorsa ve ilgili seçenek kullandığı kararlı Rust sürümünde mevcut değilse şu rustfmt seçeneklerini uygulamama tercihinde bulunabilir:

Otomatik biçimlendirmeyi devre dışı bırakmak

#[rustfmt::skip] kullanarak otomatik biçimlendirmeyi devre dışı bırakabilirsiniz; ancak yalnızca şu durumlarda:

Önemli: Kişisel stil tercihleri için biçimlendirme atlanmamalıdır.

Sahiplik ve ödünç alma

Rc ve Arc'tan kaçının

Bellek yönetimi için Rc ve Arc yerine benzersiz sahipliği ve ödünç almayı tercih edin.

İstisna:

Gerekçe

Genel olarak Rust, nesne grafiklerini paylaşımlı sahiplik akıllı işaretçileri kullanmadan temsil etmeye izin verir. Bu işaretçiler çalışma zamanı yükü ve kendilerine özgü karmaşıklık getirir. Başka dillerden gelen mühendisler Rc ve Arc'ı çok hızlı kullanmaya eğilimli olabilir.

Derin kopyalar yerine ödünç almayı tercih edin

Bir nesnenin gerçekten derin kopyasına ihtiyaç duyduğunuzda .clone() kullanın.

.clone()'ı ödünç almanın yerine geçen bir çözüm olarak kullanmaktan kaçının. Rust'ın sahiplik ve ödünç alma kurallarıyla doğal biçimde çalışan kod tasarlayın. Ancak ödünç alma ilişkileri aşırı karmaşık hâle gelirse .clone() pragmatik bir seçim olabilir.

Klonlama ile ödünç alma arasında seçim yaparken kodun performans gereksinimlerini göz önünde bulundurun.

Not: Bu tavsiye Rc veya Arc işaretçilerinin klonlanması için geçerli değildir. Referans sayımlı işaretçilerin klonlanması derin kopya oluşturmaz ve onları kullanmanın beklenen bir yoludur.

Gerekçe

Derin kopyalar, bellek tahsisi ve veri kopyalama nedeniyle pahalı olabilir.

Fonksiyon girdileri ve çıktıları

Değişmez veriler için esnek referanslar kabul edin

Fonksiyonlar ödünç alınmış, değişmez veri kabul ettiğinde, en fazla esnekliği sağlayan referans türünü kullanın. Akıllı işaretçilere, kapsayıcılara veya sarmalayıcılara referanslar yerine verinin kendisine referans geçin.

Kullanın Yerine Neden?
&T &Box<T> Box'ın içindeki veriyi ödünç alın
&[T] &Vec<T> Belirli kapsayıcılar yerine herhangi bir dilimi kabul edin
&str &String Sahiplenilmiş string'lere referans yerine string dilimi kabul edin
Option<&T> &Option<T> Option'ın içindeki veriyi ödünç alın

İstisnalar:

Bağlam için Clippy lint'lerine bakın: borrowed_box ve ptr_arg.

Gerekçe

Belirli akıllı işaretçileri, kapsayıcıları veya sarmalayıcıları anmayan referans türlerini kabul etmek fonksiyonları daha çok yönlü ve performanslı kılar: çağıranlar, veriyi önce kopyalamadan çeşitli biçimlerde tutulan veriyi geçebilir.

Dönüş türlerinde iterator'lar ve sahiplenilmiş koleksiyonlar

Mümkün olduğunda Vec<T> döndürmek yerine iterator (somut türler veya impl Iterator) döndürmeyi tercih edin.

Not: Uygulama kodunda, kodu basitleştiriyorsa ve iterator'ların esneklik ile performans avantajları açıkça gerekli değilse Vec<T> döndürmek sorun değildir.

İstisnalar:

Gerekçe

Yüksek düzeyde yeniden kullanılabilir kütüphane kodu genellikle daha esnek ve performanslı API'ler sunmaya çalışmalıdır. Iterator döndürmek, çağıranların veriyi nasıl tüketeceğini seçmesine olanak tanır: bir vektöre toplamak, bir döngüde işlemek veya başka iterator adaptörleriyle zincirlemek. Bu seçeneklerden bazıları kullanıcıların gereksiz tahsislerden kaçınmasını sağlar.

Uygulama kodunda bir fonksiyonun kullanım kalıpları genellikle daha kolay öngörülür. Performansın sorun olmadığı veya verinin zaten bir vektöre toplanacağı açıksa, vektör döndürmek daha basit bir API sunar.

Fonksiyon parametrelerinde iterator'lar ve dilimler

Yüksek düzeyde yeniden kullanılabilir kütüphane kodu, girdi üzerinde yalnızca iterasyon yapması gerekiyorsa iterator veya impl IntoIterator kabul etmelidir.

Not: Uygulama kodunda, kodu basitleştiriyorsa ve iterator'ların esneklik ile performans avantajları açıkça gerekli değilse &[T] dilimini kabul etmek sorun değildir.

Gerekçe

Yüksek düzeyde yeniden kullanılabilir kütüphane kodu genellikle daha esnek ve performanslı API'ler sunmaya çalışmalıdır. Iterator kabul eden fonksiyonlar çağıranlara şunları sunar:

Uygulama kodunda bir fonksiyonun kullanım kalıpları genellikle daha kolay öngörülür. Performansın sorun olmadığı veya çağıranların zaten her zaman bir vektöre sahip olacağı açıksa, dilim kabul etmek daha basit bir API sunar.

Into kabul etmek

Bir T kabul eden kütüphane API'leri tasarlarken kullanıcıların çeşitli türlerde değerlere sahip olması muhtemelse, çağrı yerindeki ergonomi her şeyden önemliyse T yerine impl Into<T> kabul etmeyi düşünün.

İpucu: Bu, kod şişkinliği kaygılarıyla dengelenmelidir; kodunuzda çok sayıda Into<T> kullanmak bir zorunluluk değildir.

Not: Uygulama kodu neredeyse hiçbir zaman impl Into<T> parametreleri kullanmamalıdır.

Gerekçe

impl Into<T> kullanmak, bir fonksiyonun çeşitli uyumlu türlerdeki çeşitli argümanları doğrudan kabul etmesini sağlar. Örneğin impl Into<String> kabul eden bir fonksiyon, doğrudan bir String veya bir &str literali ile çağrılabilir.

Buradaki ödünleşim, çağrı yerinde .into() yazmaktan kaçınmanın kolaylığı ile generic'lerin maliyeti (daha karmaşık API imzası, artan kod boyutu, bazen tür çıkarımı zorlukları) arasında denge kurmaktır.

API'lerde bool yerine enum tercih edin

Parametrenin amacının açık olmadığı durumlarda bool yerine enum fonksiyon parametreleri kullanın.

Yine de bool kullanıyorsanız, argüman listesindeki bool'un amacını belgelemek için satır içi bir yorum kullanmayı düşünün.

Hataları raporlama

API sözleşmenize uyan hata raporlama mekanizmasını seçin: Option, Result veya panic!.

Basit başarısızlıklar için Option kullanın: nedeni açıksa ve çağıran daha fazla ayrıntıdan fayda görmeyecekse. Bu, eksik bir değer veya ayrıştırma uyuşmazlığı gibi standart, öngörülen sonuçlar için en iyisidir.

// İyi: Başarısızlığın nedeni açık (tamsayı taşması). Makul biçimde daha
// fazla ayrıntı sağlanamazdı.
fn checked_add(a: u32, b: u32) -> Option<u32> { ... }

Çağıranın bağlama ihtiyaç duyduğu kurtarılabilir başarısızlıklar için Result kullanın. Bunlar genellikle dış etkenlerden (G/Ç veya ağ gibi) kaynaklanan ve doğru uygulanmış kodun bile önceden yapılan kontrollerle engelleyemeyeceği "kaçınılmaz" hatalardır. Yine de çağıran, hata türlerini makul biçimde öngörebilir (dosyayla ilgili bir fonksiyon G/Ç hatasıyla başarısız olabilir). Hata ayrıntıları, çağıranın başarısızlık kiplerini ayırt etmesine ve uygun şekilde kurtarmasına olanak tanır.

// İyi: Çağıranın *neden* başarısız olduğunu bilmesi gerekir (NotFound veya
// PermissionDenied).
fn open_file(path: &Path) -> Result<File, io::Error> { ... }

Hataları ve ihlal edilen değişmezleri (invariant) raporlamak için panic! kullanın. Bu hatalar önlenebilirdir: programda bir kontrol eksiktir ve bir ön koşulu yerine getirmeden bir işlemi çağırmaktadır. Bu kusurlar çalışma zamanında ele alınmak yerine kodda düzeltilmelidir; yine de catch_unwind ile kaba taneli bir kurtarma mümkün olabilir.

// İyi: Değişmez (indeks sınırlar içinde olmalı) ihlal edilirse panikler.
// Bu, çağıranın mantığındaki bir hataya işaret eder.
impl<T> MyVec<T> {
    pub fn remove(&mut self, index: usize) -> T {
        if index >= self.len() {
            panic!("index out of bounds");
        }
        // ...
    }
}

Not: Bazı kod tabanları paniklerden tamamen kaçınmayı ve bozuk değişmezlerin bile Result aracılığıyla raporlanmasını zorunlu tutmayı seçebilir.

İstisnalar:

Türler

Newtype'lar ve tür takma adları

Şu durumlarda tür takma adı yerine newtype kullanın:

Tuple'lar ve newtype olmayan tuple struct'lar yerine adlandırılmış alanları tercih edin

Tuple indeksleri (.0, .1, ...) hızla kafa karıştırıcı hâle geldiği için tuple'lar yerine adlandırılmış alanlara sahip türleri tercih edin.

İstisnalar:

Trait'ler

Trait implementasyonlarını gerektiğinde ekleyin

Genel bir kural olarak, düşük seviyeli kütüphane kodu kullanıcıların ihtiyaçlarını proaktif olarak öngörmelidir; uygulama kodu ise trait impl'lerini gerektiğinde eklemelidir.

Özel metotlar yerine bilinen standart kütüphane trait'lerini uygulayın

Bir metot, bir standart kütüphane trait'inin amacına ve davranışına uyuyorsa o trait'i uygulayın. Örneğin Foo üzerinde fn from_str(&str) -> Self yazmak yerine impl FromStr for Foo yapmalısınız.

Keşfedilebilirlik açısından hem metoda hem de trait impl'ine sahip olmak kabul edilebilir.

Bağlam için Clippy lint'ine bakın: should_implement_trait (trait listesi).

Mümkünse #[derive()] kullanın

#[derive()] ile ifade edilebiliyorsa elle yazılan uygulamalardan kaçınılmalıdır.

Generic'ler ve dyn Trait

Kütüphanelerde fonksiyon parametrelerinde dyn Trait yerine generic'leri (T: Trait veya impl Trait) kabul etmeyi tercih edin.

İstisnalar:

Gerekçe

Ne zaman trait, ne zaman inherent implementasyon tanımlanmalı

Bir trait'i yalnızca şu amaçlarla tanımlayın:

Bir tür için işlevsellik uygulamak istediğiniz diğer tüm durumlarda bir inherent implementasyon kullanın.

Trait'leri yalnızca kodu düzenlemek için kullanmayın.

Extension trait'ler

Extension trait, farklı bir crate'ten gelen bir türe metotlar ekleyen bir trait'tir.

Bir extension trait'i diğer trait'lerden ayıran şey, tek amacının türe metotlar eklemek olmasıdır; generic'lerde (T: Trait, impl Trait) veya dinamik gönderimde (dyn Trait) kullanılmak üzere tasarlanmamıştır. Bunu belirtmek için extension trait'in adı ~Ext son ekiyle bitmelidir (örneğin ForeignTypeExt).

Genel olarak, özgün türün sahibi olan ekip extension trait'in de sahibi olmalıdır.

İstisnalar:

Not: Bu kural özellikle extension trait'lerle ilgilidir. Bir trait'i girdi trait'i olarak tanımlamak (ör. DateLike) ve onu bir dizi yabancı tür üzerinde uygulamak oldukça yaygındır.

Örnekler

extern crate foreign_crate;
use foreign_crate::ForeignType;

trait ForeignTypeExt {
  fn additional_method(&self);
}

impl ForeignTypeExt for ForeignType {
  fn additional_method(&self) { ... }
}

Ayrıca yabancı bir trait'i uygulayan her tür için bir extension trait tanımlamak da mümkündür. Etkili olarak bu, yabancı trait'e varsayılan metotlar eklemeye eşdeğerdir:

extern crate foreign_crate;
use foreign_crate::ForeignTrait;

trait ForeignTraitExt {
  fn additional_method(&self);
}

impl<T: ForeignTrait> ForeignTraitExt for T {
  fn additional_method(&self) { ... }
}

Gerekçe

Extension trait ile genişletilen türün aynı ekibe ait olması, temel tür ile extension trait arasında veya iki extension trait arasında metot adı çakışmalarını önlemeye yardımcı olur.

Önemli trait'ler için özel yönergeler

Önemli From impl'lerini açık kurucu metotlarla eşleştirin

Bir türün, onu oluşturmanın birincil yollarından biri olması beklenen önemli bir From impl'i varsa, ona bir inherent ::new(), ::new_from_foo() veya ::from_foo() metodu ekleyin.

Seçenek struct'ları Default uygulamalıdır

Tümü herkese açık alanlardan oluşan ve öncelikli olarak bir fonksiyon için "seçenekler torbası" olarak kullanılan struct'lar Default uygulamalıdır.

Gerekçe

Bu, struct güncelleme sözdiziminin (Struct { a: 0, ..Default::default()}) kolayca kullanılmasını sağlar.

Eşzamanlılık

Zehirlenmeyle başa çıkma

Kullanım durumu ne kadar cazip görünürse görünsün, zehirlenmiş (poisoned) bir mutex'in içine erişmeye asla kalkışmayın.

async / .await kullanmayın [chrome]

Chromium'da async / .await kullanmayın.

Rust standart kütüphanesi üzerinden yeni thread başlatmayın [chrome]

Chromium'da thread'ler yerine sequence'ları kullanmayı tercih edin.

fuchsia_sync::Mutex kullanın [Fuchsia]

std::sync veya parking_lot içindekiler yerine Fuchsia'nın mutex türünü kullanın.

Makrolar

Bir API'yi nasıl ifade edeceğinizi değerlendirirken şu sırayı tercih edin:

Bir makro veya kod üretimi kullanmaya karar verdiğinizde lütfen faydaları maliyetlere karşı tartın:

Gerekçe

Makro dışı olanakları tercih edin: Makrolar, mühendislerin kod tabanı genelinde çalışmasını zorlaştıran dil lehçeleri oluşturabilir. Bir okuyucu bir makro kullanımı gördüğünde, o kod hakkında makro kullanılmasaydı yapabileceğinden daha az varsayımda bulunabilir. Makroları doğru yazmak sıradan Rust kodundan daha zordur.

Basit kod üretimi için macro_rules! kullanın: Rust makroları AST üzerinde çalıştığı için metinsel kod üretiminden daha az kırılgan olabilir.

Adlandırma

Metotları adlandırma

Metotları adlandırırken Rust standart kütüphanesinin stilini izleyin. Ayrıca Comprehensive Rust kursundaki adlandırma kuralları yönergelerine de bakın.

Özel hususlar:

Lifetime'ları adlandırma

Lifetime'lar, özellikle tür lifetime'ları, açıklayıcı adlara sahip olmalıdır.

İstisnalar:

Generic parametreleri adlandırma

Generic parametreleriniz için temsil ettikleri şeyin türüyle ilgili kısa bir ad (tek veya çok karakterli) kullanın. Tek harfli generic'ler sorun değildir; ancak seçilen harf nesne türü hakkında bir fikir vermeli, ideal olarak en önemli trait sınırından alınmalıdır.

Generic'ler görece sınırsız bir bağlamdayken, yani Vec<T> gibi çoğu türle kullanılmak üzere tasarlanmış bir API olduğunda T veya U sorun değildir.

Extension trait'leri adlandırma

Bir extension trait adlandırırken ada ~Ext son ekini verin (örneğin ForeignTypeExt).

Başlatma fonksiyonlarını adlandırma

Başlatma fonksiyonlarını adlandırırken şu yönergeleri izleyin:

Kısaltmalardan kaçının

Projenizin dışındaki birinin bilmesinin olası olmadığı kısaltmaları (özellikle akronimleri ve baş harf kısaltmalarını) kullanmayın. Bir sözcüğün içindeki harfleri silerek kısaltmayın.

Ayrıca Google C++ Stil Rehberi'ne bakın.

İstisnalar:

Dokümantasyon ne zaman ve nasıl yazılır

Herkese açık API'lere dokümantasyon yorumları ekleyin; bunda missing_docs lint'i yardımcı olabilir.

Dokümantasyon yorumları için en iyi uygulamalar:

İstisnalar:

Dokümantasyon yorumları nasıl biçimlendirilir

Dokümantasyon yorumları yazarken şu yönergeleri izleyin:

Doc yorumlarını (/// ve //!) sıradan yorumlarla (//) karıştırmak kabul edilebilir, hatta teşvik edilir. Herkese açık olarak belgelemek istediğiniz bir şey ve uygulama yorumu olarak not etmek istediğiniz başka bir şey varsa ikisini birden kullanın! Örneğin:

/// Verilen `name` ile ilk `FooItem`'ı bulur, yoksa `None` döndürür.
// TODO: crbug.com/12345 - `name` String değil, &str olmalı.
pub fn get_named(items: &[FooItem], name: String) -> Option<&FooItem> {...}

Gerekçe

Pek çok dilde (hepsinde değil), doc yorumları ile uygulama yorumları için farklı yorumlama gelenekleri vardır (örneğin fazladan * karakterleri). Rust ve Python yalnızca farklı yorumlama geleneklerine sahip olmakla kalmaz, tamamen farklı bir sözdizimine de sahiptir. Bunun Rust ekosistemi için somut faydaları vardır:

Diğer Rust özellikleri

Unsafe kod

Aynı işlevsellik güvenli kodla uygulanamıyorsa unsafe kod yazmayın.

Unsafe kod yazmanız gerekiyorsa:

Kararsız özellikler yok

Kararsız (unstable) özellikleri kullanmayın.

İstisnalar:

Getter'lar ve setter'lar

Getter ve setter'ların yazımı ve adlandırılması şu yönergeleri izlemelidir:

Örnekler

impl Stuff {
   fn foo(&self) -> &Foo { ...  }
   fn foo_mut(&mut self) -> &mut Foo { ... }

   /// "baz" özelliğini `None` olarak ayarlar ve eski değeri döndürür.
   fn take_baz(&mut self) -> Option<Baz> { ... }

   fn set_foo(&mut self, foo: Foo) { ... }

   /// `f`'i "bar" özelliğinin bir ödünç alımıyla çağırır.
   /// `f`'in dönüş değerini döndürür.
   fn with_bar<R>(&self, f: impl FnOnce(&Bar) -> R) -> R { ... }

   /// `f`'i "bar" özelliğinin değiştirilebilir bir ödünç alımıyla çağırır.
   /// `f`'in dönüş değerini döndürür.
   fn with_mut_bar<R>(&self, f: impl FnOnce(&mut Bar) -> R) -> R { ... }

   /// "foo" özelliğini `foo` olarak ayarlar ve eski değeri döndürür.
   fn replace_foo(&mut self, foo: Foo) -> Foo  { ... }
}

Closure kabul eden erişimciler hakkında daha fazlası

Closure kabul eden erişimciler (.with_foo()) tipik olarak object.with_foo(|foo| do_something_with(foo)) şeklinde çağrılır. Şu durumlarda iyi bir seçim olabilirler:

Bir with_foo() erişimcisi genellikle closure'ın bir değer döndürmesine ve bunu with_foo()'dan döndürerek iletmesine izin verir.

Biri salt okunur, diğeri değiştirilebilir erişim için iki closure kabul eden erişimci sağlamanız gerekiyorsa bunları with_foo() ve with_mut_foo() olarak adlandırmak yaygındır. Aksi hâlde with_foo() değiştiren erişimciler için de sıkça kullanılır.

Beklenen API kümelerini birlikte uygulayın

len() uyguluyorsanız is_empty() de uygulayın. Bağlam için Clippy lint'ine bakın: len_without_is_empty.

Sahibi olmadığınız #[doc(hidden)] olarak işaretlenmiş öğelere erişmeyin

#[doc(hidden)] olarak işaretlenmiş öğeleri özel (private) kabul edin. Kodu sizin değilse bu öğelere erişmeyin.

const fonksiyonları sorumlu bir şekilde kullanın

Herkese açık bir fonksiyonu yalnızca gelecekte de const olarak kalmasını bekliyorsanız (veya değiştiğinde istemcilerin geçişine yardımcı olmayı taahhüt edebiliyorsanız) const olarak işaretleyin.

Gerekçe

Bir const fonksiyonu normal bir fonksiyona dönüştürmek geriye dönük uyumsuz (breaking) bir değişikliktir.

Matematiksel olmayan amaçlarla operatör aşırı yüklemesinden kaçının

Operatör aşırı yüklemelerini yalnızca kodu daha kısa hâle getirmek için ("sözdizimsel şeker" olarak) değil, özgün matematiksel/mantıksal amaçları için kullanın.

Herkese açık enum'lara #[non_exhaustive] uygulayın

Bir enum, diğer ekipler tarafından kullanılan herkese açık bir API'nin parçasıysa onu #[non_exhaustive] olarak işaretleyin.

İstisnalar:

Bağlam için Clippy lint'ine bakın: exhaustive_enums.

Gerekçe

Bir enum'u #[non_exhaustive] olarak işaretlemek, kullanıcıları match ifadelerinde bir joker kol (_ => ...) eklemeye zorlar. Bu, enum'a daha sonra yeni varyantlar eklendiğinde kodun derlenmeye devam etmesini sağlar.

Bu kural öncelikli olarak ekip sınırları arasında kullanılan enum'lar için önemlidir; çünkü kırılmanın kapsamı büyük olabilir. Yalnızca az sayıda yerde kullanılan enum'lar büyük bir kırılma yaratamaz ve bu nedenle bu kuralı yok sayabilir.

Herkese açık alanlara sahip herkese açık struct'lara #[non_exhaustive] uygulayın

Diğer ekipler tarafından kullanılan herkese açık bir API'nin parçası olan, herkese açık alanlara sahip struct'lar #[non_exhaustive] olarak işaretlenmelidir.

İstisnalar:

Gerekçe

Tüm alanları herkese açık olan bir struct'a yeni bir alan eklemek (özel bir alan bile olsa) geriye dönük uyumsuz bir değişikliktir; çünkü çağıranlar artık bir struct literalinin tüm alanlarını adlandıramaz.

#[non_exhaustive] bir struct, aynı crate içindeki kod dışında struct literal sözdizimiyle oluşturulamaz. Bu, kullanıcıları bozmadan yeni özel alanlar eklenmesine olanak tanır.

Bu kural öncelikli olarak ekip sınırları arasında kullanılan struct'lar için önemlidir; çünkü kırılmanın kapsamı büyük olabilir. Yalnızca az sayıda yerde kullanılan struct'lar büyük bir kırılma yaratamaz ve bu nedenle bu kuralı yok sayabilir.

Fonksiyonlarda #[must_use]

Şu durumlarda fonksiyonlarda #[must_use] kullanın:

Türlerde #[must_use]

Yukarıdaki kurallara göre aksi hâlde #[must_use] olarak işaretlenecek fonksiyonlar tarafından döndürülmek üzere tasarlanmış türlerde #[must_use] kullanın.

Örnekler:

main() öncesinde kod çalıştırmayın

Örneğin ctor crate gibi teknikler kullanarak main() öncesinde kod çalışmasına neden olmayın.

Uygulama hususları

Tür çıkarımını aşırı kullanmayın

Kod tabanına aşina olan birinin çıkarılan türlerin ne olduğunu anlayabilmesi için yeterince açık tür ekleyin.

Türleri turbofish olarak eklemek yerine değişken bildirimlerine eklemeyi tercih edin. Derleyici sizi bir turbofish eklemeye zorluyorsa genellikle bir _ çıkarım işaretçisi eklemek sorun değildir.

Muhtemelen tür eklemeniz gereken özel durumlar:

Kontrol et-sonra-unwrap et yerine desen eşleştirmeyi tercih edin

Girdi verisi panikleyemeyeceklerini garanti ediyorsa panikleyebilecek işlemlerden kaçının. Örneğin geçerli olduğu bilinen bir değer üzerinde Option::unwrap kullanmak yerine, doğruluğu derleyiciye kanıtlamak için desen eşleştirmeyi kullanın.

// İyi: match kolu, `x`'in `Some` olduğunu derleyiciye kanıtlar.
// Bir panik açıkça mümkün değildir.
if let Some(value) = option {
    use_value(value);
}
// Kötü: `unwrap`'ın doğruluğu, `option`'ın `Some` olduğuna dair çalışma
// zamanı kontrolüne dayanır.
if option.is_some() {
    use_value(option.unwrap());
}

unwrap yerine expect tercih edin

Bir değişmez tür sistemi içinde ifade edilemediği için panik gerekiyorsa, nedeni belgelemek için unwrap yerine expect tercih edin.

Eşleştirirken non_exhaustive enum'larda desenleri atlamayın

non_exhaustive enum'lar üzerinde match ifadelerinde tüm enum varyantlarını listelemeyi tercih edin.

Belirli varyantlarla ilgilenmiyorsanız onları joker kola koyun. Yalnızca belirli varyantlarla ilgileniyorsanız bir if let veya let … else {} kullanın.

Örnekler:

#[non_exhaustive]
pub enum Shape {
    Circle,
    Square,
    Triangle,
}
fn process_shape(shape: Shape) {
  match shape {
    Shape::Circle => println!("It's a circle"),
    Shape::Square => println!("It's a square"),
    // Triangle ve gelecekteki varyantlar herhangi bir işlem gerektirmez.
    Shape::Triangle | _ => {}
  }
}
fn process_shape(shape: Shape) {
  if let Shape::Circle = shape {
    println!("It's a circle");
  }
}
fn process_shape(shape: Shape) {
  match shape {
    Shape::Circle => println!("It's a circle"),
    _ => {}
  }
}

İstisnalar:

Doğrulamaları netleştirmek için bağlam dizeleri kullanın

Bazı doğrulamalar kendilerini açıklasa da çoğu, bir doğrulamanın neden gerçekleşmesinin beklendiğini veya ihlaline neyin yol açtığını netleştiren ek bağlamdan fayda görür. Bu bağlamı iletmek için assert!'in isteğe bağlı format dizesi argümanını kullanın.

Bağlam eklemeyi her zaman düşünmelisiniz; özellikle şu durumlarda:

Örnekler

Bir doğrulamanın neden gerçekleşmesi gerektiğini açıklamak

    assert!(
        values.is_sorted(),
        "`values` is populated from a `PriorityQueue`, so we expect it to be sorted"
    );

Doğrulamanın neden başarısız olduğunu netleştirmek için bir değeri günlüğe kaydetmek

assert!(refund > 0, "Refund must be positive, got {refund:?}");

Bir API'nin yanlış kullanımına işaret etmek

impl Database {
    ...

    fn execute_query(&self, query: &str) {
        assert!(self.stream.is_some(), "Call connect() before executing queries");
    }

    ...
}

Bağlam dizesini yalnızca koşulu tekrarlamak için kullanmayın

assert!(values.is_sorted(), "values should always be sorted");

Doğrudan iterasyon daha açıkken iterator kombinasyon zincirleri kullanmayın

Kombinasyon zincirlerini okunabilirliği bozacak şekilde kullanmaktan kaçının. Özellikle:

Kombinasyonlar, daha kısa zincirlerde ve kombinasyonun kendisinin, karşılık gelen for döngüsünde yazılması gerekecek çok miktarda mantık sağladığı durumlarda tercih edilir.

Örnekler

Karmaşık kombinasyon zincirleri

Koşullar içerdiği ve dış durumu değiştirdiği için okunması zor olan bir kombinasyon zinciri örneği:

let mut error_count = 0;
let _processed_items: Vec<_> = items
    .into_iter()
    .map(transformation_that_may_fail)
    .map(|result| {
        if result.is_err() {
            error_count += 1;
        }
        result
    })
    .filter_map(Result::ok)
    .collect();

Karşılık gelen for döngüsü daha kolay okunur:

let mut error_count = 0;
let mut processed_items = Vec::with_capacity(items.len());

for item in items {
    match transformation_that_may_fail(item) {
        Ok(val) => processed_items.push(val),
        Err(_) => error_count += 1,
    }
}

For döngülerinde kombinasyon zincirleri

Bir for döngüsünün başlığında kombinasyon zinciri oluşturmaktan kaçının:

for bug in bug_descriptions
    .iter()
    .filter_map(|description| parse_bug_description(description).ok())
    .filter(|bug| bug.priority <= Priority::P1)
{
    assign_bug(bug);
}

Bunun yerine kombinasyon zincirini bir değişkene atayın. Bu dağınıklığı azaltır ve değişken adı kombinasyon zincirinin ne yaptığını belgeler:

let urgent_bugs = bug_descriptions
    .iter()
    .filter_map(|description| parse_bug_description(description).ok())
    .filter(|bug| bug.priority <= Priority::P1);

for bug in urgent_bugs {
    assign_bug(bug);
}

Kombinasyon zinciri ne zaman tercih edilmeli

Kombinasyon zincirinin tercih edildiği bir örnek:

let threshold_exceeded = values.iter().any(|&v| v > threshold);

Karşılık gelen for döngüsü daha zor okunur ve any() içindeki mantığı yeniden icat eder:

let mut threshold_exceeded = false;
for v in values {
  if v > threshold {
    threshold_exceeded = true;
    break;
  }
}

panic::catch_unwind çağırmayın

Birinci taraf Rust kodunda panic::catch_unwind kullanımı yasaktır.

İstisnalar:

Gerekçe

Rust kodu birçok ortamda kullanılabilir ve panic::catch_unwind yalnızca panic=unwind olan ortamlarda doğru çalışır. Üretim ikili dosyalarının nihai panik davranışı henüz belirlenmemiş olsa da Chrome, Android ve Fuchsia'nın üçü de panic=abort gerektirir.

Birleştirme ve yerine koyma için format! / write! tercih edin

Şu durumlarda diğer alternatifler yerine format!() ve write!() tercih edin:

Gerekçe

Kod tabanına uygun test çerçevesini kullanın

Çoğu kod tabanı #[test] yerine kendi özel test çerçevesini kullanır.


Bu belge, Google Rust Style Guide metninin Türkçe çevirisidir. Özgün metin CC BY 4.0 lisansıyla yayımlanmaktadır; çeviri de aynı lisans altında sunulmaktadır.