LoginSignup
0
0

Rustのマクロにおけるスライス型の扱いについて

Last updated at Posted at 2023-12-22

少し考えれば当然の話ですが、これのせいで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] ではない
  • スライス型を捕捉したいときは、まず参照型を処理する必要がある
  1. 厳密に言うと可能です。その場合unsafe Rustを使用し自身でメモリ安全を保障する必要があります。

0
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0