Rust'ta println!, format!, write! ve eprintln! makroları aynı biçim dilini kullanır: metindeki {} yer tutucusu değeri Display trait'iyle, {:?} ise Debug trait'iyle yazar. İki nokta üst üsteden sonra genişlik, hizalama, dolgu karakteri, işaret, ondalık hassasiyeti ve sayı tabanı gibi seçenekler eklenir: {:>8.2} değeri 8 karakterlik alana sağa yaslı ve virgülden sonra 2 basamakla yazar. Kendi tiplerinizi yazdırmak için Debug genellikle türetilir, Display ise her zaman elle uygulanır.
Bu yazıda konumsal ve adlandırılmış argümanları, satır içi değişken yakalamayı, biçim belirteçlerinin tam sözdizimini, Display ve Debug uygulamayı, uygulamanızın genişlik ve hizalama seçeneklerine uymasını sağlayan Formatter::pad metodunu, debug_struct yardımcılarını ve tipik derleme hatalarını çalışan örneklerle anlatıyoruz. Türetilebilen trait'lerin genel resmi için derive yazısına, makroların kendisinin nasıl yazıldığı için macro_rules! yazısına bakın. Serinin tamamı Rust öğrenme rehberi sayfasında.
Biçim dizesi derleme anında denetlenir
std::fmt belgesine göre biçim dizesi bir string literal olmak zorundadır; bir değişkende tutulan metni biçim dizesi olarak veremezsiniz. Bunun nedeni, derleyicinin dizeyi derleme sırasında ayrıştırıp argüman listesinin uygun olup olmadığını denetlemesidir. Eksik argüman, kullanılmayan argüman veya ilgili trait'i uygulamayan bir değer, programı çalıştırmadan önce derleme hatası olarak karşınıza çıkar. Tek bir değeri metne çevirmek için ayrıca to_string metodu vardır; bu metot Display uygulamasını kullanır.
Her {} varsayılan olarak "sıradaki argümanı" alır. Argümanı açıkça seçmek için sıra numarası ({0}, {1}) veya ad ({ad}) yazabilirsiniz. Belgede vurgulanan bir incelik vardır: numara veya ad belirten yer tutucular, numarasız {} yer tutucularının iç sayacını ilerletmez. Adlandırılmış argümanlar listenin sonunda ad = ifade biçiminde verilir; listede olmayan bir ad yazarsanız makro kapsamdaki aynı adlı değişkeni kullanır. Süslü parantezin kendisini yazmak için ikilersiniz: {{ ve }}.
fn main() {
let ad = "Zeynep";
let puan = 87;
println!("{} {} {}", 1, 2, 3);
println!("{2} {1} {0}", "a", "b", "c");
println!("{1} {} {0} {}", 1, 2);
println!("{ad} {puan} puan aldı");
println!("{isim}: {}", puan, isim = "Not");
println!("{{süslü}} parantez, {}", "{boş}");
let s = format!("{ad}-{puan}");
let t = puan.to_string();
println!("{s} {t} {}", s.len());
}
// çıktı: 1 2 3
// çıktı: c b a
// çıktı: 2 1 1 2
// çıktı: Zeynep 87 puan aldı
// çıktı: Not: 87
// çıktı: {süslü} parantez, {boş}
// çıktı: Zeynep-87 87 9Üçüncü satırdaki "{1} {} {0} {}" çıktısının 2 1 1 2 olması, belgedeki "numaralı yer tutucular sayacı ilerletmez" kuralının sonucudur: ilk numarasız {} hâlâ birinci argümanı, ikinci numarasız {} ikinci argümanı alır. Okunabilirlik için aynı dizede iki stili karıştırmamak en iyisidir. {ad} biçimindeki satır içi yakalama yalnızca bir değişken adı için çalışır; {kisi.ad} veya {a + b} gibi ifadeler yazılamaz, bunlar için argümanı ayrıca verirsiniz.
Biçim belirtecinin anatomisi

Belgedeki dilbilgisine göre bir yer tutucunun iki nokta sonrası kısmı şu sırayı izler: [[dolgu]hizalama][işaret][#][0][genişlik][.hassasiyet][tip]. Her parça isteğe bağlıdır ama sıra sabittir; örneğin dolgu karakteri her zaman hizalama işaretinden hemen önce gelir.
- Genişlik: değerin kaplayacağı en az karakter sayısı. Değer kısaysa dolgu eklenir, uzunsa kesilmez.
{:5}sabit,{:1$}birinci konumdaki argümandan,{:genislik$}adlandırılmış argümandan alınır; başvurulan argümanusizeolmalıdır. - Hizalama ve dolgu:
<sola,^ortaya,>sağa yaslar. Önündeki karakter dolgu olur:{:-<10}. Varsayılan dolgu boşluktur; sayı olmayan değerler sola, sayılar sağa yaslanır. - İşaret:
+pozitif sayılarda da işaret yazar. - Alternatif biçim
#:{:#?}girintili Debug,{:#x}önüne0x,{:#b}önüne0b,{:#o}önüne0oekler. - Sıfır dolgu
0: tam sayılarda işareti dikkate alarak sıfırla doldurur; sıfırlar işaret ve önekten sonra gelir. - Hassasiyet: ondalıklı sayılarda virgülden sonraki basamak sayısı, metinde en fazla karakter sayısıdır (fazlası kesilir), tam sayılarda yok sayılır.
.N,.N$veya.*ile verilir. - Tip: boşsa
Display,?iseDebug,xveXonaltılık,osekizlik,bikilik,eveEbilimsel gösterim,pişaretçi adresidir.
fn main() {
println!("[{:<8}] [{:^8}] [{:>8}]", "sol", "orta", "sağ");
println!("[{:*^9}] [{:08.3}] [{:+}]", "ğüş", 3.14159, 42);
println!("[{:5}] [{:<5}] [{:05}] [{:05}]", 42, 42, 42, -42);
println!("{:x} {:X} {:#x} {:#010b} {:o} {:e}", 255, 255, 255, 5, 8, 1234.5);
println!("[{:.3}] [{:10.2}] [{:.0}] [{:.0}]", "kelebek", 2.0 / 3.0, 2.5, 3.5);
let genislik = 6;
let basamak = 1;
println!("[{:genislik$.basamak$}] [{:>1$}]", 9.87, 4);
// Hizalanmış tablo
for (urun, fiyat) in [("Çay", 12.5), ("Simit", 15.0), ("Ayran", 20.25)] {
println!("{:<8}|{:>8.2} TL", urun, fiyat);
}
}
// çıktı: [sol ] [ orta ] [ sağ]
// çıktı: [***ğüş***] [0003.142] [+42]
// çıktı: [ 42] [42 ] [00042] [-0042]
// çıktı: ff FF 0xff 0b00000101 10 1.2345e3
// çıktı: [kel] [ 0.67] [2] [4]
// çıktı: [ 9.9] [ 4]
// çıktı: Çay | 12.50 TL
// çıktı: Simit | 15.00 TL
// çıktı: Ayran | 20.25 TLÇıktıda birkaç kural görünür. Genişlik bayt değil karakter sayısıyla hesaplanır: "ğüş" altı bayt olmasına rağmen 9 karakterlik alanın ortasına üç karakter olarak yerleşir, "Çay" da tabloda doğru hizalanır. {:.0} ile 2.5 değeri 2, 3.5 değeri 4 olur; belge, kesme sırasında IEEE 754'ün varsayılanı olan "yarımı çifte yuvarla" kuralının kullanıldığını belirtir. {:#010b} biçiminde genişliğe önek de dahildir. {:>1$} ise genişliği birinci konumdaki argümandan (4) alır ve sıfırıncı argümanı (9.87) değil, sayacın sıradaki argümanını yazar; bu yüzden ikinci kutuda 4 görünür. Belge bu karışıklığa karşı dolar sözdiziminde adlandırılmış argüman kullanmayı önerir.
Display uygulamak: kullanıcıya yönelik çıktı

Display trait'i tek bir metot ister: fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result. Gövdede genellikle write!(f, ...) çağrılır. Display uygulamak ToString trait'ini de kendiliğinden sağlar; belge ToString yerine her zaman Display uygulamanızı önerir. Display türetilemez, çünkü kullanıcıya neyin nasıl gösterileceğine tip karar verir. Bir tipin yalnızca bir Display uygulaması olabileceği için belge, tek ve "doğal" bir metin biçimi yoksa Path::display gibi ayrı sarmalayıcı (adapter) metotlar kullanmayı önerir.
Önemli bir ayrıntı: write!(f, ...) ile yazılan bir Display, çağıranın verdiği genişlik ve hizalama seçeneklerini kendiliğinden uygulamaz. {:>12} ile yazdırdığınızda dolgu beklersiniz ama gelmez. Seçeneklere uymak için metni önce oluşturup f.pad(&metin) çağırın; pad genişlik, dolgu, hizalama ve hassasiyeti (fazlasını keserek) uygular. Sayısal tiplerde benzer iş için pad_integral vardır. f.alternate(), f.width() ve f.precision() metotlarıyla seçenekleri kendiniz de okuyabilirsiniz:
use std::fmt;
struct Para {
kurus: i64,
}
struct Etiket(String);
impl fmt::Display for Para {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let metin = format!("{},{:02} TL", self.kurus / 100, self.kurus % 100);
f.pad(&metin) // genişlik ve hizalamaya uyar
}
}
impl fmt::Display for Etiket {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if f.alternate() {
write!(f, "#{}", self.0.to_uppercase())
} else {
write!(f, "#{}", self.0) // seçenekleri yok sayar
}
}
}
fn main() {
let p = Para { kurus: 123456 };
println!("[{}]", p);
println!("[{:>14}]", p);
println!("[{:-<14}]", p);
println!("[{:.5}]", p);
let e = Etiket(String::from("ılık"));
println!("[{:>10}] [{:#}]", e, e);
let s: String = p.to_string();
println!("{}", s);
}
// çıktı: [1234,56 TL]
// çıktı: [ 1234,56 TL]
// çıktı: [1234,56 TL----]
// çıktı: [1234,]
// çıktı: [#ılık] [#ILIK]
// çıktı: 1234,56 TLEtiket için {:>10} hiçbir dolgu üretmedi, çünkü uygulama write! ile yazıyor. Ayrıca to_uppercase metodunun "ılık" için ILIK verdiğine dikkat edin: noktasız "ı" harfinin büyüğü "I" olur, beklendiği gibi. Tersine noktalı "i" için Unicode kuralı "I" verir, Türkçedeki "İ" değil; yerel kurallar standart kütüphanenin kapsamında değildir. Belge bir kuralı daha vurgular: fmt metodu yalnızca Formatter bir hata döndürdüğünde Err döndürmelidir. Biçimlendirme kavramsal olarak hatasız bir işlemdir; doğrulama hatalarını fmt::Error ile bildirmeye çalışmayın.
Debug uygulamak: türetmek ya da yardımcıları kullanmak
Belgeye göre Debug, programcıya yönelik çıktı içindir ve bütün açık (public) tiplerde uygulanmalıdır; çoğu durumda #[derive(Debug)] yeterlidir. Elle yazmanız gereken iki tipik durum vardır: bir alanı (örneğin bir parolayı) çıktıdan gizlemek ve çıktıyı daha anlamlı bir biçimde göstermek. Formatter bunun için debug_struct, debug_tuple, debug_list, debug_set ve debug_map yardımcılarını sunar. Bu yardımcılarla yazılan uygulamalar, türetilmiş olanlar gibi {:#?} girintili biçimini kendiliğinden destekler. finish_non_exhaustive ise gösterilmeyen alanlar olduğunu .. ile belirtir:
use std::fmt;
struct Kullanici {
ad: String,
parola: String,
roller: Vec<&'static str>,
}
impl fmt::Debug for Kullanici {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Kullanici")
.field("ad", &self.ad)
.field("roller", &self.roller)
.finish_non_exhaustive() // parola gösterilmez
}
}
fn main() {
let k = Kullanici {
ad: String::from("deniz"),
parola: String::from("gizli123"),
roller: vec!["admin", "editör"],
};
println!("{:?}", k);
println!("{:#?}", k);
println!("{}", k.parola.len());
println!("{} {:?} {:?}", 'a', 'a', "satır\nsonu");
}
// çıktı: Kullanici { ad: "deniz", roller: ["admin", "editör"], .. }
// çıktı: Kullanici {
// çıktı: ad: "deniz",
// çıktı: roller: [
// çıktı: "admin",
// çıktı: "editör",
// çıktı: ],
// çıktı: ..
// çıktı: }
// çıktı: 8
// çıktı: a 'a' "satır\nsonu"Son satır Display ile Debug arasındaki farkı özetler: Debug metinleri tırnak içinde ve kaçış dizileriyle yazar, böylece boşluk veya satır sonu gibi görünmez karakterleri fark edersiniz. Debug belgesine göre türetilmiş ve standart kütüphane tiplerinin Debug biçimleri kararlı değildir; bu çıktıyı ayrıştırmaya veya testlerde birebir karşılaştırmaya dayandırmayın. Hızlı hata ayıklama için dbg! makrosu da vardır: ifadenin değerini Debug ile, dosya ve satır bilgisiyle birlikte standart hataya (stderr) yazar ve değeri geri döndürür. Değer üzerinde sahipliği aldığı için Copy olmayan tiplerde dbg!(&x) yazmanız gerekir. Belge, dbg! satırlarının sürüm kontrolünde uzun süre kalmamasını önerir.
write!, format! ve diğer makrolar
Aynı biçim dili birkaç makroda ortaktır. format! bir String üretir. print! ve println! standart çıktıya, eprint! ve eprintln! standart hataya yazar; hata ve tanı mesajlarını eprintln! ile yazmak, programınızın çıktısı bir dosyaya yönlendirildiğinde mesajların karışmamasını sağlar (bkz. komut satırı yazısı). write! ve writeln! ilk argüman olarak bir hedef alır: std::fmt::Write (örneğin String) veya std::io::Write (örneğin dosya, Vec<u8>). Belgeye göre amaç ara bir String ayırmadan doğrudan hedefe yazmaktır. Bütün bu makroların temelinde format_args! vardır ve heap ayırmadan bir fmt::Arguments değeri üretir.
use std::fmt::Write as FmtWrite;
use std::io::Write as IoWrite;
fn main() {
let mut rapor = String::new();
for (i, sehir) in ["İstanbul", "Muğla"].iter().enumerate() {
writeln!(rapor, "{}. {}", i + 1, sehir).unwrap();
}
write!(rapor, "toplam {}", 2).unwrap();
println!("{rapor}");
let mut baytlar: Vec<u8> = Vec::new();
write!(&mut baytlar, "ok={}", true).unwrap();
println!("{:?}", baytlar);
let argumanlar = format_args!("{} + {} = {}", 2, 3, 2 + 3).to_string();
println!("{argumanlar}");
}
// çıktı: 1. İstanbul
// çıktı: 2. Muğla
// çıktı: toplam 2
// çıktı: [111, 107, 61, 116, 114, 117, 101]
// çıktı: 2 + 3 = 5fmt::Write ve io::Write trait'lerinin ikisinin de adı Write olduğu için, aynı dosyada ikisini birden içe aktarırken as ile farklı adlar verilir. String hedefine yazmak pratikte başarısız olmaz, ama dönüş tipi fmt::Result olduğu için sonucu işlemeniz beklenir; aksi halde derleyici kullanılmayan Result uyarısı verir.
Tipik derleme hataları

Biçim dizesi derleme anında denetlendiği için hataların çoğu çalıştırmadan önce yakalanır. En sık görülen, Display uygulamayan bir tipi {} ile yazdırmaya çalışmaktır. Vec, Option ve tuple gibi tiplerin Display uygulaması yoktur, yalnızca Debug uygulamaları vardır. Derleyici hata mesajının devamında {:?} kullanmayı önerir:
fn main() {
let v = vec![1, 2, 3];
println!("{}", v);
}
// error[E0277]: `Vec<{integer}>` doesn't implement `std::fmt::Display`Argüman sayısı uyuşmazlığı da derleme hatasıdır. Belge, biçim dizesinin bütün argümanları kullanmak zorunda olduğunu açıkça belirtir; fazla bir argüman verdiğinizde hata alırsınız:
fn main() {
println!("{} ve {}", "çay", "kahve", "su");
}
// error: argument never used| Yazım | Örnek | Çıktı |
|---|---|---|
| {} | format!("{}", 3.5) | 3.5 |
| {:?} | format!("{:?}", "a") | "a" |
| {:#?} | format!("{:#?}", (1, 2)) | girintili çok satır |
| {:>6} | format!("{:>6}", "ab") | ab |
| {:*^6} | format!("{:*^6}", "ab") | **ab** |
| {:06.2} | format!("{:06.2}", 3.14159) | 003.14 |
| {:+} | format!("{:+}", 7) | +7 |
| {:#x} | format!("{:#x}", 255) | 0xff |
| {:08b} | format!("{:08b}", 5) | 00000101 |
| {:e} | format!("{:e}", 1500.0) | 1.5e3 |
| {ad} | format!("{ad}") | kapsamdaki ad değişkeni |
| {{ }} | format!("{{}}") | {} |
Sık yapılan hatalar
- Biçim dizesini değişkenden vermek.
println!(sablon, x)derlenmez; biçim dizesi literal olmalıdır. Değişken metni olduğu gibi yazdırmak içinprintln!("{}", sablon)kullanın. Displayuygulamasında genişliği yok saymak.write!dolgu uygulamaz; tablo hizası gerekiyorsaf.padkullanın.- Debug çıktısına bağımlı olmak. Biçim kararlı değildir; kullanıcıya gösterilecek veya kaydedilecek metin için
Displayyazın. - Hassasiyeti yuvarlama sanmak.
{:.0}2.5 için 2 verir; "yarımı çifte" kuralı geçerlidir. - Satır içi yakalamada ifade yazmak.
{kisi.ad}geçerli değildir;{}ile argüman verin. - Locale beklemek. Rust biçimlendirmesi yerel ayarlardan bağımsızdır; ondalık ayırıcı her zaman noktadır.
fmtiçinde kendi hatanızı döndürmek.fmt::Erroryalnızca yazma hedefinden gelen hatayı aktarmak içindir.
Sık Sorulan Sorular
Display ile Debug arasındaki fark nedir?
Display son kullanıcıya yöneliktir, elle uygulanır ve her tipin olması beklenmez. Debug programcıya yöneliktir, iç durumu olabildiğince olduğu gibi gösterir, genellikle türetilir ve bütün açık tiplerde bulunmalıdır.
Ondalık sayıyı iki basamakla nasıl yazdırırım?
{:.2} kullanın: format!("{:.2}", 3.14159) sonucu 3.14 olur. Genişlikle birlikte {:8.2} yazabilirsiniz.
Türkçe karakterler hizalamayı bozar mı?
Rust genişliği karakter (Unicode skaler değeri) sayısıyla hesaplar, bayt sayısıyla değil; bu yüzden ç, ğ, ı, ö, ş, ü içeren metinler doğru hizalanır.
Vec veya Option değerini {} ile neden yazdıramıyorum?
Bu tiplerin Display uygulaması yoktur ve orphan kuralı yüzünden siz de ekleyemezsiniz. {:?} kullanın ya da kendi sarmalayıcı tipinize Display uygulayın; newtype deseni trait yazısında anlatılıyor.
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 · Rust derive · Rust String ve str · Rust trait · Rust komut satırı argümanları · E0277 tip hataları