はじめに
よくC言語とかだとこういうのやると思います。
#include <stdio.h>
#include<stdlib.h>
typedef struct TypeStruct{
int type;
}TypeStruct;
typedef struct TypeA{
TypeStruct type;
char name[100];
int age;
}TypeA;
TypeStruct* createAbstruct(){
TypeA* a = (TypeA*)malloc(sizeof(TypeA));
TypeA initialize={{0},"Lisa",18};
*a = initialize;
return (TypeStruct*)a;
}
void abstructReader(TypeStruct* ts){
// 本当はこの辺りに型判定が入る。
TypeA* ta=(TypeA*)ts;
printf("My name is %s. Age is %d years old",ta->name,ta->age);
}
int main(void){
TypeStruct* st = createAbstruct();
abstructReader(st);
}
みたいなやつです。
これを、Rustでやってみた。
(某Ter○tailにやりかた質問しようとして質問を書いてたら自己解決したのでメモ。)
↑解決したのはしたけど、Rust的に安全に書きましょう!というところから逸脱していた。
というわけで、@ubnt_intrepidさんがコメントくれたものが実際に動いた&安全なので推奨版をアップデートします。
助かりました、ありがとうございます!
推奨版(Trait objectを使う)
// foo.rs
pub trait Reader {
fn read(&self) -> Result<(), Box<dyn std::error::Error>>;
}
pub fn create_abstract_reader() -> Box<dyn Reader> {
struct PrivateReader {
// type id は不要
name: String,
age: u32,
}
impl Reader for PrivateReader {
fn read(&self) -> Result<(), Box<dyn std::error::Error>> {
...
}
}
Box::new(PrivateReader { name: "Alice".into(), age: 42 })
}
// main.rs
fn main() {
let reader = foo::create_abstract_reader();
reader.read();
}
非推奨(Boxで生ポインタを使う)
// mainとは別のソースにある。
// もしかすると別モジュールかもしれない。
// 実装はモジュールが知っている。
// main関数は公開されたインタフェースのみ(PublicPointer・FooStructのみ)を
// 把握しているが、細かくカスタムされた内部実装は何も知らない。
// 公開インタフェース
#[repr(C)]
pub struct PublicPointer_impl{
struct_type:u32
}
// 公開インタフェース
pub type PublicPointer=* const PublicPointer_impl;
// 公開インタフェース
pub trait RequireTrait{
// 書き込み
fn write(core:FooStruct)->Result<PublicPointer,Box<std::error::Error>>;
// 読み込み
fn read(st:PublicPointer)->Result<(),Box<std::error::Error>>;
}
// privateなデータ
#[derive(Debug,Clone)]
#[repr(C)]
pub struct Private{ // 呼び出しを簡単にするためpubにしてるが、本来はPrivateな構造体
struct_type:u32,
foo:FooStruct,
custom_data:u128
}
// 公開インタフェース
#[derive(Debug,Clone)]
pub struct FooStruct{
pub test:String,
pub test1:String,
}
impl RequireTrait for Private{
fn write(foo:FooStruct)->Result<PublicPointer,Box<std::error::Error>>{
let p = Box::new(Private{foo:foo,struct_type:102,custom_data:200});
// Privateな型のポインタを返す(struct_type=10の型)
let p:*mut Private = Box::into_raw(p);
let pp = p as PublicPointer;
Ok(pp)
}
fn read(raw:PublicPointer)->Result<(),Box<std::error::Error>>{
let st = raw as *mut Private;
println!("private struct : {:?}",st);
println!("st {}",unsafe{(*st).clone().foo.test});
Ok(())
}
}
// main.rs
mod testtrait;
use crate::testtrait::*;
fn main() {
let foo = FooStruct{test:"test".to_string(),test1:"test1".to_string()};
let private = Private::write(foo).unwrap();
Private::read(private);
}
API設計をする時にこういう技は使いたくなる。
というわけで、こんな感じ。
Box化してBoxのポインタをそのまま返却する感じ。
Boxマジ便利。
Trait Objectマジ便利