Rust'ta belgelendirme yorumları üç eğik çizgiyle (///) yazılır ve hemen ardından gelen öğeyi belgeler; //! ile başlayan yorumlar ise içinde bulundukları öğeyi, genellikle crate'in kendisini ya da bir modülü belgeler. Bu yorumlar Markdown destekler. cargo doc komutu Rust ile gelen rustdoc aracını çalıştırıp HTML belgeleri target/doc klasörüne üretir, cargo test ise belgelerdeki kod örneklerini test olarak derleyip çalıştırır. Böylece örnekleriniz kodla birlikte eskimez.

Bu yazıda rustdoc aracının temel kullanımını, iyi bir belge yorumunun yapısını, belge testlerini ve özniteliklerini, öğelere adla bağlantı vermeyi, rustdoc lint'lerini ve #[doc] özniteliğini çalışan bir kütüphane üzerinden ele alıyoruz. Normal birim ve entegrasyon testleri için cargo test yazısına, lint düzeylerinin genel mantığı için Clippy ve rustfmt yazısına bakabilirsiniz.

rustdoc ve cargo doc ne yapar?

rustdoc ve cargo doc ne yapar? --open, --no-deps, --document-private-items, -p, --package

rustdoc: What is rustdoc? sayfasına göre rustdoc argüman olarak bir crate kökü ya da Markdown dosyası alır ve HTML, CSS ile JavaScript üretir. Doğrudan rustdoc src/lib.rs çalıştırırsanız crate adını dosya adından çıkarmaya çalışır; doğru adı --crate-name ile vermeniz gerekir. Ayrıca varsayılan olarak yalnızca herkese açık öğeleri belgeler: pub olmayan bir fonksiyonun yorumu sayfada görünmez.

Cargo projelerinde bu ayrıntılarla uğraşmazsınız. cargo doc doğru --crate-name değerini, çıktı klasörünü ve bağımlılık yollarını kendisi verir; arka planda yaptığı çağrıyı cargo doc --verbose ile görebilirsiniz. cargo-doc(1) belgesine göre öne çıkan seçenekler şunlardır:

SeçenekNe yapar?
--openBelgeleri oluşturduktan sonra tarayıcıda açar
--no-depsBağımlılıkların belgelerini oluşturmaz
--document-private-itemsHerkese açık olmayan öğeleri de ekler; ikili hedeflerde varsayılan olarak açıktır
-p, --packageYalnızca belirtilen paketleri belgeler
--workspaceÇalışma alanındaki tüm üyeleri belgeler

Belgeye göre belge üretimi birikimlidir: target/doc altındaki eski dosyalar sonraki çağrılarda korunur. Eski belgeleri temizlemek için cargo clean --doc kullanılır. Varsayılan olarak cargo doc paketin tüm kütüphane ve ikili hedeflerini belgeler; kütüphaneyle aynı adı taşıyan ikili hedef atlanır.

/// ve //! arasındaki fark

/// dış (outer) belgelendirmedir ve kendisinden sonra gelen öğeye, örneğin bir fonksiyona, yapıya ya da modüle aittir. //! iç (inner) belgelendirmedir ve içinde bulunduğu öğeyi belgeler. Crate kökünden önce hiçbir şey gelmediği için crate'in tamamını belgelemenin yolu src/lib.rs dosyasının başına //! satırları yazmaktır. Kitap, aynı yöntemin bir modül dosyasının başında modülü bir bütün olarak anlatmak için de kullanıldığını belirtir.

Normal yorumlar (//) belgeye girmez; kodun nasıl uygulandığını anlatmak için kalır. Kitabın ayrımıyla, belgelendirme yorumları crate'inizi kullanmak isteyen programcılar içindir, nasıl uygulandığını merak edenler için değil. Yorum türlerinin genel kullanımı fonksiyonlar ve yorumlar yazısında anlatılıyor.

İyi bir belge yorumunun yapısı

rustdoc: How to write documentation sayfası her öğe için şu iskeleti önerir: ne olduğunu anlatan kısa bir cümle, daha ayrıntılı bir açıklama, kullanıcının kopyalayıp deneyebileceği en az bir kod örneği ve gerekirse daha ileri açıklamalar. İlk boş satıra kadar olan metin arama sonuçlarında ve modül özetlerinde yeniden kullanılır; bu yüzden özeti tek satırda tutmak iyi bir alışkanlıktır. Belge ayrıca tip sisteminin fonksiyonun aldığı ve döndürdüğü tipleri zaten anlattığını, rustdoc imzadaki tüm tiplere bağlantı eklediği için bunları metinde tekrarlamanın fayda getirmediğini söyler.

Kitap ve rustdoc belgeleri sık kullanılan başlıkları da sayar. Bunların hepsi her öğede gerekmez, ama bir kontrol listesi olarak işe yarar:

  • # Examples: öğenin nasıl kullanıldığını gösteren çalışan kod.
  • # Panics: fonksiyonun hangi durumda panik oluşturduğu; çağıran bu durumlardan kaçınabilsin diye.
  • # Errors: Result döndüren fonksiyonlarda hangi hataların hangi koşulda geldiği.
  • # Safety: unsafe bir fonksiyonu çağıranın neyi garanti etmesi gerektiği. Ayrıntılar unsafe Rust yazısında.

Örnek kütüphane: belgelenmiş bir crate

Aşağıdaki src/lib.rs dosyası cargo new --lib belge ile oluşturulan bir projede denendi. Crate düzeyinde //! açıklaması, her herkese açık öğe için /// yorumları, farklı türde belge testleri ve öğeler arası bağlantılar içeriyor.

rust
//! Basit fiyat hesaplama araçları.
//!
//! Bu crate, KDV eklemek ve indirim uygulamak için küçük
//! yardımcılar sunar. Ana tip [`Fiyat`]'tır.

#![warn(missing_docs)]

/// Kuruş cinsinden tutulan bir fiyat.
///
/// # Examples
///
/// ```
/// use belge::Fiyat;
///
/// let f = Fiyat::new(1000);
/// assert_eq!(f.kurus(), 1000);
/// ```
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Fiyat(u64);

impl Fiyat {
    /// Kuruş değerinden yeni bir [`Fiyat`] oluşturur.
    pub fn new(kurus: u64) -> Self {
        Fiyat(kurus)
    }

    /// İçteki kuruş değerini döndürür.
    pub fn kurus(&self) -> u64 {
        self.0
    }

    /// Yüzde olarak verilen KDV'yi ekler; sonuç aşağı yuvarlanır.
    ///
    /// # Examples
    ///
    /// ```
    /// # use belge::Fiyat;
    /// let f = Fiyat::new(1000).kdv_ekle(20);
    /// assert_eq!(f.kurus(), 1200);
    /// ```
    pub fn kdv_ekle(self, yuzde: u64) -> Self {
        Fiyat(self.0 + self.0 * yuzde / 100)
    }

    /// İndirim uygular.
    ///
    /// # Panics
    ///
    /// `yuzde` 100'den büyükse panik oluşur.
    ///
    /// ```should_panic
    /// # use belge::Fiyat;
    /// Fiyat::new(500).indirim(150);
    /// ```
    pub fn indirim(self, yuzde: u64) -> Self {
        assert!(yuzde <= 100, "indirim yüzdesi 100'ü aşamaz");
        Fiyat(self.0 - self.0 * yuzde / 100)
    }
}

/// Tam lira yazılmış bir metinden fiyat ayrıştırır, örneğin `"12"`.
///
/// # Errors
///
/// Metin sayı değilse [`std::num::ParseIntError`] döner.
///
/// ```
/// # use belge::ayristir;
/// let f = ayristir(" 12 ")?;
/// assert_eq!(f.kurus(), 1200);
/// # Ok::<(), std::num::ParseIntError>(())
/// ```
///
/// Fiyat tipi değiştirilemez bir değerdir:
///
/// ```compile_fail
/// let f = belge::Fiyat::new(1);
/// f.0 = 5;
/// ```
pub fn ayristir(metin: &str) -> Result<Fiyat, std::num::ParseIntError> {
    Ok(Fiyat(metin.trim().parse::<u64>()? * 100))
}

Belge testleri cargo test ile diğer testlerle birlikte, ya da yalnız başına cargo test --doc ile çalışır. Çıktı Windows'ta alındığı için yollar ters bölüyle görünüyor:

bash
$ cargo test --doc
   Doc-tests belge

running 4 tests
test src\lib.rs - Fiyat (line 12) ... ok
test src\lib.rs - Fiyat::indirim (line 51) - should panic ... ok
test src\lib.rs - Fiyat::kdv_ekle (line 36) ... ok
test src\lib.rs - ayristir (line 67) ... ok

test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s

running 1 test
test src\lib.rs - ayristir (line 76) - compile fail ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.04s

all doctests ran in 0.31s; merged doctests compilation took 0.24s

Belge testleri nasıl çalışır?

Belge testleri nasıl çalışır? unused_variables, dead_code gibi küçük örneklerde sık tetiklenen bazı allow..., #![doc(test(attr(...)))] ile verilen öznitelikler eklenir; baştaki...

rustdoc: Documentation tests sayfasına göre belge testleri normal birim testleri gibi değerlendirilir: derlenip panik oluşturmadan çalışırsa geçer. Bir sonucu doğrulamak için assert_eq! gibi makroları kullanırsınız; değer değişirse test başarısız olur. Örnekte her kod bloğunun başına fn main yazmamıza gerek kalmadı, çünkü rustdoc örnekleri çalıştırmadan önce ön işlemden geçirir. Belgedeki algoritmanın özeti şudur:

  • unused_variables, dead_code gibi küçük örneklerde sık tetiklenen bazı allow öznitelikleri eklenir.
  • #![doc(test(attr(...)))] ile verilen öznitelikler eklenir; baştaki #![...] öznitelikleri crate özniteliği olarak korunur.
  • Örnek extern crate içermiyorsa ve bu davranış kapatılmamışsa sizin crate'iniz için extern crate <crate_adı>; eklenir.
  • Örnek fn main içermiyorsa geri kalan kod fn main() { ... } içine sarılır.

# ve bir boşlukla başlayan satırlar derlenir ama belgede gösterilmez. Örnekteki # use belge::Fiyat; satırları bu sayede okuyucuyu yormaz. Bu teknik özellikle ? operatörü için gereklidir: ? bir Result döndürür, ama sarılmış main hiçbir şey döndürmediği için tip uyuşmazlığı oluşur. Belge iki çözüm verir: gizli satırlarla Result döndüren bir main yazmak ya da örneğin sonuna # Ok::<(), HataTipi>(()) eklemek. İkincisinde (()) kısmı arada boşluk olmadan yazılmalıdır, aksi halde rustdoc örneği Result döndüren bir fonksiyon olarak tanımaz. Hata tipini açıkça yazmak da gerekir, çünkü ? örtük bir dönüşüm eklediği için tip çıkarımı tek bir sonuca varamaz.

Belge ayrıca örneklerde unwrap() yerine bu tür gizli hata yönetimini tercih etmenizi önerir; okuyucu örneği kendi koduna kopyaladığında daha iyi bir alışkanlık edinir. Bir örneğin değeri değiştiğinde ne olduğunu görmek için kdv_ekle örneğindeki beklenen değeri bilerek 1300 yaptık. Test şu çıktıyla başarısız oldu:

bash
---- src\lib.rs - Fiyat::kdv_ekle (line 36) stdout ----
Test executable failed (exit status: 101).

stderr:

thread 'main' (77464) panicked at C:\Users\...\doctest_bundle_2024.rs:28:1:
assertion `left == right` failed
  left: 1200
 right: 1300

Geçici dosya yolunu kısalttık; iş parçacığı numarası her çalıştırmada değişir. İlginç bir ayrıntı daha: aynı dosyadaki ayristir örneğinde de 1200 değeri geçtiği için sed ile yaptığımız değişiklik iki testi birden bozdu ve ikisi de rapora girdi.

Kod bloğu öznitelikleri

Kod çitinin açılış satırına yazılan öznitelikler, rustdoc'a örneği nasıl ele alacağını söyler. Dil belirtilmemiş bir blok Rust kodu kabul edilir; Rust olmayan bir içerik için text yazmanız gerekir.

ÖznitelikDavranışNe zaman?
(yok) veya rustDerlenir ve çalıştırılırVarsayılan; çoğu örnek için
should_panicDerlenmeli ve panik oluşturmalı# Panics bölümündeki örnekler
no_runDerlenir ama çalıştırılmazAğ erişimi gerektiren ya da sonsuz döngülü örnekler
compile_failDerleme başarısız olmalıTip sisteminin neyi engellediğini göstermek
ignoreHiç derlenmezEksik ya da sözde kod; nadiren doğru seçim
edition2024 vb.Belirtilen edition ile derlenirEdition farkını göstermek
standalone_crateDiğer testlerle birleştirilmezSatır numarası gibi ortama bağlı örnekler
textRust kodu sayılmazÇıktı veya düz metin

Belge ignore özniteliği için uyarır: en genel seçenektir ve neredeyse hiçbir zaman istediğiniz şey değildir; kod değilse text, kodsa # satırlarıyla çalışan bir örnek tercih edilmelidir. Kullanacaksanız nedenini parantez içinde yazmak gelenektir. compile_fail için de bir not düşer: bugünkü sürümde derlenmeyen kod, dile yeni özellikler eklendikçe ileride derlenebilir. Örneğimizdeki compile_fail testi, dış crate'ten özel alana erişilemediğini gösteriyor.

Rust 2024: birleştirilmiş belge testleri

Çıktının son satırındaki merged doctests compilation ifadesi bir 2024 edition değişikliğidir. Edition Guide'a göre önceki edition'larda rustdoc her kod bloğunu ayrı bir çalıştırılabilir dosya olarak derliyordu ve çok sayıda örnek olduğunda bu ciddi bir yük getiriyordu. 2024 edition'da uyumlu örnekler tek bir ikili dosyada birleştirilip derlenir; her örnek bu dosyada ayrı bir fonksiyon olur ve testler yine ayrı süreçlerde çalışır. Bizim çıktımızda da dört test bir grupta, compile_fail testi ise ayrı bir grupta koştu; son satır birleştirilmiş derlemenin süresini ayrıca raporluyor.

Bazı örnekler birleştirmeye uygun değildir. Belgedeki örnek, std::panic::Location::caller() ile satır numarasını doğrulayan bir testtir: başka bir örnek eklendiğinde satır kayar ve test bozulur. Bu durumda bloğa standalone_crate özniteliği eklenir. Rustdoc bazı uyumsuz durumları kendisi de algılar. Diğer edition değişiklikleri için 2024 edition yazısına bakabilirsiniz.

rustdoc lint'leri ve missing_docs

rustdoc lint'leri ve missing_docs: missing_docs, rustdoc::broken_intra_doc_links, rustdoc::private_intra_doc_links, rustdoc::missing_crate_level_docs

rustdoc: Lints sayfası rustdoc'a özgü lint'leri listeler. Bunlar rustdoc:: önekiyle yazılır ve missing_docs dışında yalnızca rustdoc çalışırken etkindir. Öne çıkanlar:

  • missing_docs: varsayılan olarak kapalıdır ve belgesiz herkese açık öğeleri bulur. Diğerlerinden farklı olarak doğrudan rustc tarafından da sağlanır; örnekteki #![warn(missing_docs)] satırı belgesiz bir fonksiyon eklediğimizde hem cargo test sırasındaki derlemede hem cargo doc çalışırken warning: missing documentation for a function uyarısı üretti.
  • rustdoc::broken_intra_doc_links: varsayılan olarak uyarır; çözülemeyen bağlantıları bulur.
  • rustdoc::private_intra_doc_links: varsayılan olarak uyarır; herkese açık bir öğenin belgesinden özel bir öğeye verilen bağlantıları yakalar.
  • rustdoc::missing_crate_level_docs: varsayılan olarak kapalıdır; crate kökünde belge yoksa uyarır. Belgeye göre ileride uyarı düzeyine çekilmesi amaçlanıyor.
  • rustdoc::invalid_codeblock_attributes, rustdoc::bare_urls, rustdoc::invalid_html_tags: yanlış yazılmış kod bloğu öznitelikleri, bağlantıya çevrilmemiş çıplak adresler ve hatalı HTML etiketleri için.

Belge, kütüphane yazarları için #![deny(missing_docs)] özniteliğinin projenin belgesiz kalmasını önlemenin, #![warn(missing_docs)] özniteliğinin ise kapsamlı belgelendirmeye doğru ilerlemenin iyi bir yolu olduğunu söyler.

#[doc] özniteliği: hidden ve alias

/// yorumları aslında #[doc = "..."] özniteliğinin kısa yazımıdır ve bu öznitelik başka ayarlar da alır. rustdoc: The #[doc] attribute sayfasına göre #[doc(hidden)] ile işaretlenen öğe, --document-hidden-items bayrağı verilmedikçe belgede görünmez. Rustdoc belgeleri bunu, kütüphanenin içini kolaylaştıran ama kullanıcıya açılması tehlikeli olabilecek ayrıntıları gizlemek için önerir. #[doc(alias = "BaskaAd")] ise arama dizinine ek bir ad ekler; kullanıcı bu adı aradığında öğe bulunur. Belge bunun özellikle C kütüphanelerine bağlama yazarken, C tarafındaki fonksiyon adlarıyla arama yapılabilmesi için yararlı olduğunu belirtir.

Markdown ve rustdoc uzantıları

Rustdoc CommonMark Markdown belirtimini kullanır ve buna birkaç uzantı ekler: tilde ile üstü çizili metin, dipnotlar, boru ve tirelerle tablolar, görev listeleri ve akıllı noktalama. Akıllı noktalama bazı ASCII dizilerini tipografik karakterlere çevirir; örneğin art arda yazılmış iki veya üç kısa çizgi uzun çizgilere dönüşür. Belgede bu dönüşümü istemiyorsanız ilgili metni kod içine alın.

Sık yapılan hatalar

  • Özeti iki paragraf uzunluğunda yazmak; ilk boş satıra kadar olan kısım arama ve modül sayfalarında görünür.
  • ? içeren örnekte Ok::<(), HataTipi>(()) satırını unutmak ya da (()) içine boşluk koymak.
  • Çalışmayan her örneği ignore ile işaretlemek; çoğu durumda # ile gizlenen kurulum satırları ya da no_run daha doğrudur.
  • Özel bir öğeyi belgeleyip sayfada neden görünmediğini merak etmek; varsayılan çıktı yalnızca herkese açık öğeleri içerir.
  • Belge testlerinin kütüphane hedefi için çalıştığını unutmak; Cargo belgesine göre cargo test kod örneklerini kütüphane hedefinin belge yorumlarından çıkarır. Örneklerin sınanmasını istiyorsanız mantığı lib.rs dosyasına taşıyın.
  • Satır numarası gibi ortama bağlı bilgileri test eden örnekleri standalone_crate olmadan bırakmak.

Kütüphane ve ikili crate ayrımının testlere etkisi test yazısında ayrıntılı ele alınıyor; projenin lib.rs ve main.rs olarak bölünmesi için de modül sistemi yazısına bakabilirsiniz.

Sık Sorulan Sorular

Belgeyi nasıl görüntülerim?

cargo doc --open belgeleri oluşturup varsayılan tarayıcıda açar. Başka bir tarayıcı için BROWSER ortam değişkenini ya da doc.browser Cargo ayarını kullanabilirsiniz. Dosyalar target/doc/<crate_adı>/index.html altında durur.

Belge testleri ne zaman çalışır?

cargo test birim ve entegrasyon testlerinden sonra Doc-tests başlığı altında belge testlerini de çalıştırır. Yalnızca bunları çalıştırmak için cargo test --doc kullanın. Belge testlerinde derleyici uyarılarını görmek isterseniz belge, cargo test --doc -- --show-output komutunu önerir.

Bağımlılıkların belgelerini de görebilir miyim?

Evet. cargo doc varsayılan olarak yerel paketle birlikte tüm bağımlılıkları da belgeler. Yalnızca kendi crate'inizi istiyorsanız --no-deps verin. Bağımlılık tanımları için Cargo.toml yazısına bakabilirsiniz.

README dosyamı da test edebilir miyim?

Belgeye göre #[doc = include_str!("../README.md")] özniteliğini #[cfg(doctest)] ile işaretli, gizli bir yapıya uygulayarak README içindeki Rust örneklerini diğer belge testleriyle birlikte çalıştırabilirsiniz. Rustdoc cfg(doctest) koşulunu yalnızca belge testleri için derleme yaparken ayarlar; bu yapı herkese açık belgede görünmez.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: Rust öğrenme rehberi · Rust nedir? · Test yazma ve cargo test · Clippy ve rustfmt · macro_rules! ile makro yazma · Cargo.toml ve bağımlılıklar