この記事は、はじめてのアドベントカレンダー Advent Calendar 2023 の21日目です。
少し考えれば当然の話ですが、これのせいで1時間程溶かしたので共有します。
記事執筆時点の環境
awayume@assam:~/programming/playground/rust-slice-sample$ cargo -v -V
cargo 1.73.0 (9c4383fb5 2023-08-26)
release: 1.73.0
commit-hash: 9c4383fb55986096b414d98125421ab87b5fd642
commit-date: 2023-08-26
host: aarch64-unknown-linux-gnu
libgit2: 1.6.4 (sys:0.17.2 vendored)
libcurl: 8.2.1-DEV (sys:0.4.65+curl-8.2.1 vendored ssl:OpenSSL/1.1.1u)
ssl: OpenSSL 1.1.1u 30 May 2023
os: Ubuntu 22.04 (jammy) [64-bit]
awayume@assam:~/programming/playground/rust-slice-sample$ rustc -v -V
rustc 1.73.0 (cc66ad468 2023-10-03)
binary: rustc
commit-hash: cc66ad468955717ab92600c770da8c1601a4ff33
commit-date: 2023-10-03
host: aarch64-unknown-linux-gnu
release: 1.73.0
LLVM version: 17.0.2
syn: v2.0.41
この記事では syn
を使用することを前提としています。
また、unsafe Rustは使用しないという条件での内容です。
スライス型とは
この記事をどうぞ。
この記事において重要なのは、基本的にスライスは常に参照されなければならない1という点です。
型を扱う
マクロで型を扱う例として、導出マクロでstructの型を処理してみましょう。
ダメな例
use proc_macro::TokenStream;
use syn::{parse_macro_input, Item, Struct, Type};
#[proc_macro_derive(SampleDerive)]
pub fn parse_type(input: TokenStream) -> TokenStream {
// struct以外を弾く
if let Item::Struct(ast) = parse_macro_input!(input) {
let ident: Ident = ast.ident;
for field in ast.fields {
let field_ident: Ident = field.ident.unwrap();
match field.ty {
Type::Slice(_) => /* 処理 */,
_ => unimplemented!(),
}
}
}
// 省略
}
#[derive(SampleDerive)]
struct MyStruct {
sl: &[u8],
}
何がダメなのか
このコードは意味がありません。何故なら、フィールドの型が「スライス」である場合を捕捉しようとしているためです。
スライス型はあくまでも [T]
です。&[T]
ではありません。
unsafe Rustを使用しないとき、スライスはそれそのものを定義できず、常に参照を通して定義しなければなりませんが、それはあくまでも「スライスの参照」なのです。
実際に定義することはできませんが、「スライス」という型は確かに存在します。
上のコードが機能するためには、次のようなコードが必要です。
#[derive(SampleDerive)]
struct MyStruct {
sl: [T],
}
このコードの場合でもコンパイルは通ります。
しかし、実際にこのstructを使うためには、unsafe Rustを使用して自身でメモリ安全を保障する必要があります。
(@kazatsuyuさんの指摘を受け修正)
正しい例
use proc_macro::TokenStream;
use syn::{parse_macro_input, Item, Struct, Type};
#[proc_macro_derive(SampleDerive)]
pub fn parse_type(input: TokenStream) -> TokenStream {
// struct以外を弾く
if let Item::Struct(ast) = parse_macro_input!(input) {
let ident: Ident = ast.ident;
for field in ast.fields {
let field_ident: Ident = field.ident.unwrap();
match field.ty {
Type::Reference(tref) => {
if let Type::Slice(_) = *tref.elem {
// 処理
}
},
_ => unimplemented!(),
}
}
}
// 省略
}
このコードでは、参照型を捕捉した後、その元の型がスライスであるか調べています。
まとめ
- スライス型は
[T]
であり、&[T]
ではない - スライス型を捕捉したいときは、まず参照型を処理する必要がある
-
厳密に言うと可能です。その場合unsafe Rustを使用し自身でメモリ安全を保障する必要があります。 ↩