Rustでスライスを扱う場合前に進めることはできても、後ろに戻すことができない。
C言語のプログラムから移植するなどでそのような操作をしたい場合にどうしたらいいか調べてみた。
モチベーション C言語で組まれたプログラムをRustで実装する際に、配列の途中を指すようなポインタはスライスで扱うことになると思う。
スライスはC言語でいうところのポインタに加えて長さを保持しているもので(ファットポインタ)、それによって範囲外アクセスが起きないようチェックできる。
ポインタを前に進める場合に対応するには、スライスをさらにスライス取ればいい:
でポインタを戻す場合にはどうしたらいいか。
スライスでは後ろに戻した場合の安全性を確認できないため、そのような操作ができない。
ということで、生ポインタの演算に手を出してみた。
生ポインタの操作 生ポインタの取得 生ポインタを取得するには、参照から as *const T
(イミュータブルの場合)または as *mut T
(ミュータブルの場合)でキャストしてやる:
let array : [i32 ; 6 ] = [11 , 22 , 33 , 44 , 55 , 66 ];let slice : &[i32 ] = &array;let rawptr : *const i32 = &slice[0 ] as *const i32 ; println! ("*{:?} = {}" , rawptr, unsafe {*rawptr});
スライスの as_ptr メソッドでも取得できる。
生ポインタへの整数加減算 生ポインタを進めたり戻したりするには offset
メソッドを使用する:
let rawptr2 : *const i32 = unsafe { rawptr.offset (4 ) };println! ("*{:?} = {}" , rawptr2, unsafe {*rawptr2}); let rawptr3 : *const i32 = rawptr2.wrapping_offset (-2 );println! ("*{:?} = {}" , rawptr3, unsafe {*rawptr3});
C言語と同様に、整数を加減算した場合にはその値そのものじゃなく、要素サイズ倍した値がポインタ値に加算される。
wrapping_offset
というメソッドもあって、こちらは unsafe
が必要ない。
説明を読むと offset
メソッドのほうが適切に最適化される、と書いてあるがどういうことなんだろうか?
ラッピング演算でポインタが境界内でなくてもよい、ということなので感覚的には逆だと思ってしまうんだが…。
add
や sub
というのもあるが、内部では単に offset
を呼んでいるだけ。
ポインタ間の距離 ポインタ間の距離(要素数)を計算するには offset_from
をメソッド使用する:
#![feature(ptr_offset_from)] let ofs : isize = unsafe { rawptr3.offset_from (slice.as_ptr ()) }; println! ("{}" , ofs);
こちらもC言語同様に、要素サイズで割った値になる。
experimentalとのことで、 #![feature(ptr_offset_from)]
の指定が必要。
同様に wrapping_offset_from
メソッドがある。
スライスに戻す 生ポインタからスライスに変換するには、 slice::from_raw_parts
を使用する:
let restored : &[i32 ] = unsafe { std::slice::from_raw_parts (rawptr3, slice.len () - ofs as usize ) };println! ("#{}: {:?}" , restored.len (), restored);
モジュール化する 生ポインタをゴリゴリ触って unsafe
をそこら中に書き散らして未定義動作の発生を拡散させてしまっては、なんのためにRustで書いてんの?ということになってしまう。
なのでいっちょモジュール化してみる。
#![feature(ptr_offset_from)] use std::ops::Index;pub struct RewindableSlice <'a , T> { slice: &'a [T], behind_count: usize , } impl <'a , T> RewindableSlice<'a , T> { pub fn new (slice: &'a [T]) -> Self { Self { slice, behind_count: 0 , } } pub fn to_slice (&self ) -> &'a [T] { self .slice } pub fn forward (&mut self , d: usize ) { self .slice = &self .slice[d..]; self .behind_count += d as usize ; } pub fn backward (&mut self , d: usize ) { if d > self .behind_count { panic! ( "index out of bounds: the slice can moves backwards only {}, but try to move {}" , self .behind_count, d ); } let rawptr = self .slice.as_ptr (); let rawptr = unsafe { rawptr.offset ((d as isize ).wrapping_neg ()) }; self .slice = unsafe { std::slice::from_raw_parts (rawptr, self .slice.len () + d) }; self .behind_count -= d; } } impl <'a , T> Index<usize > for RewindableSlice <'a , T> { type Output = T; fn index (&self , i: usize ) -> &Self ::Output { &self .slice[i] } }
forward
で進める際に進めた数をカウントしておく。
backward
で戻る際に範囲外になってしまう場合には panic!
を起こすようにする。
使用は:
fn main () { let array : [i32 ; 6 ] = [11 , 22 , 33 , 44 , 55 , 66 ]; let mut rs = RewindableSlice::new (&array); rs.forward(4 ); println! ("{:?}" , rs.to_slice ()); rs.backward (3 ); println! ("{}" , rs[1 ]); }