ROM Okunması
Herhangi bir işlem yapmadan önce emülatör belleğimizde bir CHIP-8 programı
yüklememiz gerekli. Sonuçta emulatörümüz bu CHIP-8 programını çalıştırmaya
yarayacak. Bu işlemi gerçekleştirmek için önce bir dosya açmalı, bu dosya
içerisindeki byteları Emulator belleği'ne (memory alanı) yazmamız
gerekli. Öncelikle bu işlemi yapmak için gerekli tipleri modül içinde
kullanalım:
use std::fs::File;
use std::io;
use std::io::Read;
use std::path::Path;
Rust'ta başka bir paket ya da standart kütüphaneden herhangi bir yapı
kullanabilmek için use anahtar kelimesi kullanılır. İlk satırda
kullanılan use, std::fs modülü içerisinde yer alan File yapısını
üzerinde çalıştığımız modül içinde kullanmaya yarar. Bu sayede File
yapısında direkt ulaşabiliriz. İkinci satırda std::io modülünü kullanıyoruz.
Bu kullanımın ardından modül içerisinde yazacağımız her io,
standart kütüphanede yer alan io modülünü simgelemektedir.
Read özelliği, File üzerinde okuma yapmamız için gerekli.Son olarak dosya
yolu değişkeninde kullanacağımız Path yapısını kullanılıyoruz.
ROM okuma metodumuzu impl Emulator bloğu içerisine tanımlıyoruz:
pub fn rom_oku<P: AsRef<Path>>(mut self, path: P) -> io::Result<Emulator> {
let file = File::open(path)?;
for (loc, byte) in file.bytes().enumerate() {
self.memory[0x200 + loc] = byte?;
}
Ok(self)
}
rom_oku isimli bu metod, Emulator instance'ının bir parçası olduğundan
ilk parametresi: &mut self'dir. Instance'ın sahipliğini, bu fonksiyona
almamak için, self referans olarak kullanılır. Eğer sadece self olarak
kullansaydık, instance sahipliği bu fonksiyonun çağrılmasıyla, bu
fonksiyona geçecekti ve bir daha dışarıdan erişime izin verilmeyecekti. Bu
referansın mutable olmasının nedeni ise emulatör içerisinde yer alan bellek
(memory) alanını değiştiriyor olmamızdan kaynaklanıyor. Eğer bu referans
mutable olmasaydı, Emulator yapımızda yer alan hiç bir alanı
değiştiremezdik. path değişkeni Rust'ta referansdan-referansa dönüşüm
yapmayı mümkün kılan
AsRef
özelliğine sahip bir genelleyici seçilmiştir. Bu sayede path değişkeni
AsRef<Path> özelliğine sahip herhangi bir tip olabilir (String, &str,
OsStr, Path, PathBuf vb.).
Metodumuzda hata yönetimi yapmak için; fonksiyon tanımlamamızda dönüş
değeri, yine standart kütüphanenin io (girdi-çıktı) modülünde yer alan
Result tipini kullanıyoruz. Bizim kullandığımız io::Result tipi;
std::result::Result tipiyle karıştırılmamalıdır. Normal Result
tanımlaması, hem dönüş tipi hem de hata tipi gerektirir. Bizim bu metodda
kullanacağımız tüm hata dönebilecek fonksiyonlar io::Error tipinde
olduğundan, io::Result tipini kullanmamız yeterli. io::Result sadece
dönüş için gerekli bir tip gerektirdiğinden ve biz bu metoddan
self ile sahipliğini aldığımız Emulator instance'ını döneceğimizden,
dönüş tipi olarak Emulator kullanılır.
Dosya
let file = File::open(path)?;
ile açılıyor. Bu satırda yer alan ? hata yönetimimiz için gerekli. Bu
fonksiyon normalde
io::Result
dönüyor ve bu Result tipi hata olması
durumunda Err (bizim kullandığımız Result tipine göre io::Error),
olmaması halinde ise Ok değeri döner. ? ile yaptığımız hata kontrolü
sayesinde, bu fonksiyonun Err dönmesi durumunda, operatörümüz o noktada
gelen hatayı dönmeye yarıyor. Rust'ın bu gelişmiş hata yönetimi sayesinde,
hata kontrolü için yazılması gereken kod çok daha azalıyor ve bu basit
kullanım Rust'ın hata yönetimini çok daha güçlü hale getiriyor.
Read::bytes()
metodu, okunabilir instance'ı byteları iteratöre çevirmeye yarıyor. For
ile kullandığımız bu iterator hata olması durumunda yine io::Result dönen
bir yapıya sahip. Bu nedenle direkt byte'ı kullanmadan önce hata kontrolü
operatörümüz olan ?'ini kullanıyoruz. CHIP-8'de programlar bellekte
0x200 adresinden başladığından dolayı, belleğin 0x200 alanından itibaren
yazıyoruz.
İlk başta tanımladığımız gibi metodumuz io::Result<Emulator> dönmesi
gerekli. Fonksiyonumuz son satıra ulaştığında herhangi bir hata olmaması
durumunda, sahipliğini aldığımız instance ile birlikte Ok(self) dönüyor.
Bu sayede bu metodu kullanırken sahipliğini aldığımız instance'ı bir zincir
halinde kullanabiliriz.