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.