自己紹介
出田 守と申します。
しがないPythonプログラマです。
情報セキュリティに興味があり現在勉強中です。CTFやバグバウンティなどで腕を磨いています。主に低レイヤの技術が好きで、そっちばかり目が行きがちです。
Rustを勉強していくうえで、読んで学び、手を動かし、記録し、楽しく学んでいけたらと思います。
環境
新しい言語を学ぶということで、普段使わないWindowsとVimという新しい開発環境で行っています。
OS: Windows10 Home 64bit 1903
CPU: Intel Core i5-3470 @ 3.20GHz
Rust: 1.38.0
RAM: 8.00GB
Editor: Vim 8.1.1
Terminal: PowerShell
前回
前回はテキスト処理と正規表現について学びました。
Rust勉強中 - その23
I/O
std::ioには、RustのI/O操作に必要なRead, BufRead, Write, Seekトレイトや関数がまとめられています。
あらゆるI/Oに関する関数やメソッドの引数にreaderやwriterを渡すものがあります。
- readerはバイト列を読み出すことができる値です。
- writerはバイト列を書き出すことができる値です。
この値にはVecやファイルストリーム、ネットワークストリームなどがあります。
Readトレイト
Readトレイトは以下のように定義されています。
pub trait Read {
fn read(&mut self, buf: &mut [u8]) -> Result<usize>;
fn read_vectored(&mut self, bufs: &mut [IoSliceMut]) -> Result<usize> { ... }
unsafe fn initializer(&self) -> Initializer { ... }
fn read_to_end(&mut self, buf: &mut Vec<u8>) -> Result<usize> { ... }
fn read_to_string(&mut self, buf: &mut String) -> Result<usize> { ... }
fn read_exact(&mut self, buf: &mut [u8]) -> Result<()> { ... }
fn by_ref(&mut self) -> &mut Self
where
Self: Sized,
{ ... }
fn bytes(self) -> Bytes<Self>
where
Self: Sized,
{ ... }
fn chain<R: Read>(self, next: R) -> Chain<Self, R>
where
Self: Sized,
{ ... }
fn take(self, limit: u64) -> Take<Self>
where
Self: Sized,
{ ... }
}
readメソッドは指定されたバッファのデータソースからbyte単位で読み込みます。データがあってもbufferの長さまでしか読み込みません。読み込み時何らかのエラーが発生した場合、io::Errorを返します。さらにio::Errorはkindメソッドを使うことで、ErrorKind列挙型の値を返すことができます。ErrorKind::Interrupted値は、特にこれについて何かすることがなければ処理を再試行しても良いようです。
readメソッドの他に、バイト列を全て読み出すread_to_endメソッド、指定したバイト長を読み出すtakeメソッド、バイトのイテレータを返すbytesメソッドなどがあります。
参照:
ドキュメント - Read
BufReadトレイト
pub trait BufRead: Read {
fn fill_buf(&mut self) -> Result<&[u8]>;
fn consume(&mut self, amt: usize);
fn read_until(&mut self, byte: u8, buf: &mut Vec<u8>) -> Result<usize> { ... }
fn read_line(&mut self, buf: &mut String) -> Result<usize> { ... }
fn split(self, byte: u8) -> Split<Self>
where
Self: Sized,
{ ... }
fn lines(self) -> Lines<Self>
where
Self: Sized,
{ ... }
}
BufReadトレイトは、readerがバッファリングを利用してシステムコール呼び出しの回数を減らすことでオーバヘッドを減らすことができます。
BufReadトレイトが実装するメソッドに加え、Readトレイトを継承しているため、Readトレイトが実装するメソッドを使用することができます。
fill_bufメソッドはバイト列を読み出します。ただし、呼び出し後にreadメソッドを呼び出すと同じバイト列を読み出す可能性があるのでconsumeメソッドを使ってバイトを消費する必要があります。
read_lineやlinesは行単位で読み出すことができるメソッドです。read_lineは改行を含む行をバイト列で読み出し、linesは改行を除く行のバイト列のイテレータを返します。
以下はドキュメントの例にあったfill_bufとconsumeの使用例と、read_lineの例です。
use std::io;
use std::io::prelude::*;
fn input_line() -> io::Result<String> {
let stdin = io::stdin();
let mut line = String::new();
stdin.lock().read_line(&mut line)?;
Ok(line)
}
fn main() {
{
// fill_buf, consume
let stdin = io::stdin();
let mut stdin = stdin.lock();
let buffer = stdin.fill_buf().unwrap();
println!("{:?}", buffer);
let length = buffer.len();
stdin.consume(length);
}
// read_line
println!("{:?}", input_line());
}
aaa
[97, 97, 97, 13, 10]
aaa
Ok("aaa\r\n")
linesメソッドなどはResultを返すためエラー処理をする必要があります。そのためcollectメソッドなどのアダプタメソッドを使うときは以下のようにすると良いそうです。
fn main() {
...
// collect
use std::fs::File;
let fd = File::open("test.txt").unwrap();
let reader = io::BufReader::new(fd);
// reader.lines().collect(); // Error
let _lines: Vec<String> = reader.lines().collect::<io::Result<Vec<String>>>().unwrap(); // ok
}
これはResultのFromIterator実装のおかげでこのように書けるようです。以下がその実装です。
impl<A, E, V: FromIterator<A>> FromIterator<Result<A, E>> for Result<V, E> {
...
}
コレクション型をもつResult型はその値でのイテレータを作り出すことができるという実装です。(たぶん)
参照:
ドキュメント - BufRead
Writeトレイト
pub trait Write {
fn write(&mut self, buf: &[u8]) -> Result<usize>;
fn flush(&mut self) -> Result<()>;
fn write_vectored(&mut self, bufs: &[IoSlice]) -> Result<usize> { ... }
fn write_all(&mut self, buf: &[u8]) -> Result<()> { ... }
fn write_fmt(&mut self, fmt: Arguments) -> Result<()> { ... }
fn by_ref(&mut self) -> &mut Self
where
Self: Sized,
{ ... }
}
Writeトレイトは書き出し用のトレイトです。
writeメソッドはバッファのバイト列の一部をwriterに書き出します。flushメソッドはバッファされているデータをwriterに書き出します。
BufWriterのnewを使えば任意のwriterをバッファリングできます。BufWriterをドロップする前にはflushしておくと良いそうです。
参照:
ドキュメント - Write
std::fs
File::open
openメソッドは、読込み専用としてファイルをオープンします。
fn main() {
...
// open
let fd = File::open("test.txt").unwrap();
}
File::create
createメソッドは、書出し専用としてファイルを新しくオープンします。もし、既にファイルが存在する場合、上書きします。
fn main() {
...
// create
let fd = File::create("test2.txt").unwrap();
}
OpenOptions
OpenOptions型のメソッドを利用することで、ファイルオープン時のフラグを細かく設定できます。
例えば、以下はファイルを追記でオープンする例です。
fn main() {
...
// OpenOptions append
use std::fs::OpenOptions;
let fd = OpenOptions::new()
.append(true)
.open("test.txt").unwrap();
}
今回はここまで!