LoginSignup
163
108

Rust の型変換イディオム

Last updated at Posted at 2018-12-16

Rust の型変換イディオム

この記事は Rustその2 Advent Calendar 2018 の 12 日目の記事です。

Option<String> to Option<&str>

let a: Option<String> = Some("0".to_string());
let b: Option<&str> = a.as_deref();
// または let b: Option<&str> = a.as_ref().map(AsRef::as_ref);

Option<Vec<T>> -> Option<&[T]>

let a: Option<Vec<u8>> = Some(vec![0]);
let b: Option<&[u8]> = a.as_deref();

Option<Rc<T>> -> Option<&T>, Option<Arc<T>> -> Option<&T>

Option::as_deref は Option の中の型が Deref トレイトを実装しているときに Deref::Target の参照へ変換できる

use std::rc::Rc;
let a: Option<Rc<u8>> = Some(Rc::new(0));
let b: Option<&u8> = a.as_deref();

Option<String> to &Option<&str>

let a: Option<String> = Some("0".to_string());
let b: Option<&str> = a.as_ref().map(AsRef::as_ref);
let c: &Option<&str> = &b;

Vec<String> to &[&str]

let a: Vec<String> = vec!["0".to_string()];
let b: Vec<&str> = a.iter().map(AsRef::as_ref).collect();
let c: &[&str] = b.as_ref();

Option<Vec<String>> -> &Option<&[&str]>

let a: Option<Vec<String>> = Some(vec!["0".to_string()]);
let b: Option<&[String]> = a.as_ref().map(AsRef::as_ref);
let c: Option<Vec<&str>> = b.map(|lst| lst.iter().map(|s| s.as_ref()).collect::<Vec<_>>());
let d: Option<&[&str]> = c.as_ref().map(|lst| lst.as_ref());
let a: Option<String> = Some("0".to_owned());
let b: Option<&String> = a.as_ref();
let c: Option<&str> = b.map(|x| &**x);
let d: &Option<&str> = &c; //   ||||
                                |||+ x: &String
                                ||+ *x: String
                                |+ **x: str:  (*<String as Deref<Target = str>>::deref(*x))
                                + &**x: &str 

Option<T> to Option<&T>

let a: Option<i32> = Some(0);
let b: Option<&i32> = a.as_ref();

Result<T, E> to Result<&T, &E>

let a: Result<i32, ()> = Ok(0);
let b: Result<&i32, &()> = a.as_ref();

String -> &str

let a: String = "0".to_string();
let b: &str = &a.to_string()[..];
let a: String = "0".to_string();
let b: &str = &*a;

&str -> &str (文字列の先頭一致したいとき)

str に対して n 文字一致をしたいとき

let hoge = "hoge-huga".to_string();
match hoge.chars().take(5).collect::<String>().as_ref() {
    "hoge-" => { println!("hoge"); }
    "huga-" => { println!("huga"); }
    _ => { println!("err") }
}
  • str::get_unchecked を使いたいところだけど ascii 以外の文字列の場合、スライスの境界チェックができず unsafe

Result<T, E> -> Option<T>

let a: Result<i32, ()> = Ok(0);
let b: Option<i32> = a.ok();

Result<T, E> -> Option<E>

let a: Result<i32, ()> = Ok(0);
let b: Option<()> = a.err();

Option<T> -> Result<T, E>

let a: Option<i32> = Some(0);
let b: Result<i32, ()> = a.ok_or(());
let a: Option<i32> = Some(0);
let b: Result<i32, ()> = a.ok_or_else(|| ());

Option<T> -> Vec<T>

let a: Option<i32> = Some(0);
let b: Vec<i32> = a.into_iter().collect::<Vec<_>>();

Option<T> -> Vec<&T>

let a: Option<i32> = Some(0);
let b: Vec<&i32> = a.iter().collect::<Vec<_>>();

Vec<T> -> Option<T>

let a: Vec<i32> = vec![0];
let b: Option<i32> = a.into_iter().next();

Vec<T> -> Option<&T>

let a: Vec<i32> = vec![0];
let b: Option<i32> = a.first();

Option<Option<T>> -> Option<T>

let a: Option<Option<i32>> = Some(Some(0));
let b: Option<i32> = a.and_then(|opt| opt);

Result<Option<T>, E> -> Option<Result<T, E>>

let a: Result<Option<i32>, ()> = Ok(Some(0));
let b: Option<Result<i32, ()>> = match a {
    Ok(Some(x)) => Some(Ok(x)),
    Ok(None) => None,
    Err(e) => Some(Err(e)),
};

Option<Result<T, E>> -> Result<Option<T>, E>

let a: Option<Result<i32, ()>> = Some(Ok(0));
let b: Result<Option<i32>, ()> = match self {
    Some(Ok(x)) => Ok(Some(x)),
    Some(Err(e)) => Err(e),
    None => Ok(None),
};

Vec<Result<T, E>> -> Result<Vec<T>, E>

let a: Vec<Result<i32, ()>> = vec![Ok(0), Err(())];
let b: Result<Vec<i32>, ()> = a.into_iter().collect();
assert_eq!(b, Err(()));

Vec<Option<T>> -> Option<Vec<T>>

let a: Vec<Option<i32>> = vec![Some(0), None];
let b: Option<Vec<i32>> = a.into_iter().collect();
assert_eq!(b, None);

&T -> &[T] (Tの参照から要素1のTのスライスの参照への変換)

let a: u32 = 0;
let a_slice: &[u32] = std::slice::from_ref(&a);

&str -> &[u8]

let a: &str = "0";
let b: &[u8] = a.as_bytes();

String -> Vec<u8>

let a: String = "0".to_string();
let b: Vec<u8> = a.into_bytes();

String -> Box<str>, Rc<str>, Arc<str>

use std::rc::Rc;
use std::sync::Arc;

let a = String::from("a");

let s: Box<str> = Box::from(a.clone());
let s: Rc<str> = Rc::from(a.clone());
let s: Arc<str> = Arc::from(a.clone());

[u8] -> Box<[u8]>, Rc<[u8]>, Arc<[u8]>

use std::rc::Rc;
use std::sync::Arc;

let s: Box<[u8]> = Box::from([1,2,3]);
let s: Rc<[u8]> = Rc::from([1,2,3]);
let s: Arc<[u8]> = Arc::from([1,2,3]);

Vec<u8> -> Box<[u8]>, Rc<[u8]>, Arc<[u8]>

use std::rc::Rc;
use std::sync::Arc;

let s: Box<[u8]> = Box::from(vec![1,2,3]);
let s: Rc<[u8]> = Rc::from(vec![1,2,3]);
let s: Arc<[u8]> = Arc::from(vec![1,2,3]);

&[u8] -> &str

let a: &[u8] = &[48];
let b: &str = std::str::from_utf8(a).unwrap();
assert_eq!("0", b);

&Bytes -> &str

let b = bytes::Bytes::from("hello");
std::str::from_utf8(&b).unwrap();

Vec<u8> -> String

let a: Vec<u8> = vec![48];
let b: String = String::from_utf8(a).unwrap();
assert_eq!("0".to_string(), b);

String, &str -> u8, u16, u32 u64, usize, i8, i16, i32, i64, isize, f32, f64 (文字列から数値への変換)

let a: &str = "0";
let b: u8 = a.parse::<u8>().unwrap();

let a: String = "48".to_string();
let b: f64 = a.parse::<f64>().unwrap();

u8 -> u16 -> u32 -> u64 (安全なアップキャスト)

安全なアップキャスト

let a: u8 = 0;
let b: u16 = From::from(a);
let c = u32::from(b); // こういう風にも書ける
let d: u64 = From::from(c);

u64 -> u32 -> u16 -> u8 (安全なダウンキャスト)

let a: u64 = 255;
let b: u32 = TryFrom::try_from(a).unwrap();
let c: u16 = u16::try_from(b).unwrap(); // こうも書ける
let d: u8 = TryFrom::try_from(c).unwrap();

usize , isize を含む signed と unsigned 間のキャスト

num crate の ToPrimitiveFromPrimitive を使います

use num::FromPrimitive;
let a: Option<u8> = u8::from_usize(1_usize);
let a: Option<u8> = u8::from_isize(1_isize);
let a: Option<i8> = i8::from_u8(1_u8);
let a: Option<i64> = i64::from_usize(1_usize);

数値 -> String の桁ゼロ埋め (left-pad)

assert_eq!("00000110", format!("{:0>8}", "110"));
//                                |||
//                                ||+-- width
//                                |+--- align
//                                +---- fill

任意の struct を Vec<u8> に変換

2通りある

対象の struct がすべて自分のクレートのとき => bincode + serde を使う

#[derive(Serialize, Default)]
struct MyStruct {
    id: u8,
    data: [u8; 1024],
}

fn main() {
    let my_struct = MyStruct::default();
    let bytes: Vec<u8> = bincode::serialize(&my_struct).unwrap();
}

対象の struct が別クレート or bindgen などで自動生成されていて、Sized(コンパイル時に大きさが決まっている) のとき => std::slice::from_raw_parts を使う

unsafe fn any_as_u8_slice<T: Sized>(p: &T) -> &[u8] {
    std::slice::from_raw_parts(
        (p as *const T) as *const u8,
        std::mem::size_of::<T>(),
    )
}

#[derive(Default)]
struct MyStruct {
    id: u8,
    data: [u8; 1024],
}

fn main() {
    let my_struct = MyStruct::default();
    let bytes: &[u8] = unsafe { any_as_u8_slice(&my_struct) };
    println!("{:?}", bytes);
}

unixtimestamp(millis) -> DateTime<UTC>

use chrono::offset::TimeZone;
let dt: Option<chrono::DateTime<Utc>> = chrono::Utc.timestamp_millis_opt(0).single();

DateTime<UTC> -> unixtimestamp(millis)

let timestamp: i64 = chrono::Utc::now().timestamp_millis();

ISO8601(RFC3339) -> DateTime<Utc>

let date = chrono::DateTime::parse_from_rfc3339("1970-01-01T00:00:00.000000000Z").unwrap().with_timezone(&chrono::Utc);

DateTime<Utc> -> ISO8601(RFC3339) (millis)

let date_str = chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Millis, true); // 2018-01-26T18:30:09.453Z"

"{}" -> Option<DateTime<Utc>

use chrono::{Utc, DateTime};

#[derive(serde::Deserialize, serde::Serialize, Debug)]
struct A {
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(with = "chrono::serde::ts_milliseconds_option")]
    #[serde(default)] // 存在しないフィールドの default は None にするという指定
    pub time: Option<DateTime<Utc>>,
}
#[test]
fn test_opt_unix_millis(){
    let json = r#"{"time": 0}"#;
    assert!(serde_json::from_str::<A>(json).unwrap().time.is_some());

    let json = r#"{"time": null}"#;
    assert!(serde_json::from_str::<A>(json).unwrap().time.is_none());

    let json = r#"{}"#;
    assert!(serde_json::from_str::<A>(json).unwrap().time.is_none());

    let obj = A{time: None};
    assert_eq!(serde_json::to_string(&obj).unwrap(), r#"{}"#);
    
    let obj = A{time: Some(DateTime::parse_from_rfc3339("1970-01-01T00:00:00.000000000Z").unwrap().with_timezone(&Utc))};
    assert_eq!(serde_json::to_string(&obj).unwrap(), r#"{"time":0}"#);
}

&'a str -> Box<dyn std::error::Error + Send + Sync + 'a>

驚くべきことに std::boxed::Box

impl<'a, '_> From<&'_ str> for Box<dyn Error + Send + Sync + 'a>

実装しているので

fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
    let a_boxed_error: Box<dyn std::error::Error + Send + Sync + '_> = "error!".into();
    Err(a_boxed_error)?;
    Ok(())
}

と書くことができる

String -> Box<dyn Error + Send + Sync + 'static>

同上

let a_boxed_error: Box<dyn std::error::Error + Send + Sync + 'static> = Box::from("a".to_string());

impl std::error::Error -> std::io::Error + Send + Sync

とりあえず std::io::Error がほしいときにつかう

let io_err = std::io::Error::new(std::io::ErrorKind::Other, err);

Nullable かつ undefinedable なフィールドを含む JSON を シリアライズ | デシリアライズする

ts だと

interface Foo {
  a?: string | null;
}

json schema だと

{
  "type": "object",
  "properties": {
    "a": { "type": ["string", "null"] }
  },
  "required": []
}

のような Nullable かつ undefinable な json があった場合、 js_option を使うこと。

#[serde(skip_serializing_if = "Option::is_none")] を使った場合

serde の skip_serializing_if を使った場合、 シリアライズ時にはうまくいくが、デシリアライズ時に null と unefined の区別がつかない。

#[derive(serde::Deserialize, serde::Serialize, Debug, PartialEq, Eq)]
struct A {
    // nullable
    a: Option<i32>,

    // undefinable
    #[serde(skip_serializing_if = "Option::is_none")]
    b: Option<i32>,

    // nullable かつ undefinable
    #[serde(skip_serializing_if = "Option::is_none")]
    c: Option<Option<i32>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    d: Option<Option<i32>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    e: Option<Option<i32>>,

    // nullable
    f: Option<Option<i32>>,
    g: Option<Option<i32>>,
    h: Option<Option<i32>>,
    
}

fn main() {
    let original = A{
        // nullable
        a:None,
        // undefinable
        b:None,
        // nullable かつ undefinable
        c: None, d: Some(None), e: Some(Some(0)),
        // nullable
        f: None, g: Some(None), h: Some(Some(0))
    };
    let json = serde_json::to_string_pretty(&original).unwrap();
    println!("{}", json);
    /*
    {
      "a": null,
      "d": null,
      "e": 0,
      "f": null,
      "g": null,
      "h": 0
    }
    */
    let parsed = serde_json::from_str::<A>(&json).unwrap();
    println!("{:?}", parsed);
    // A { a: None, b: None, c: None, d: None, e: Some(Some(0)), f: None, g: None, h: Some(Some(0)) }
    assert_eq!(original, parsed);
}

Vec<(String, String)> -> HashMap<String, String>

let a = [
  ("hoge".into(), "fuga".into()),
  ("foo".into(), "bar".into())
].into_iter().collect::<HashMap<String, String>>();

serde の enum に ToString と FromStr を実装する

#[derive(serde::Deserialize, serde::Serialize)]
enum Foo {
  Hoge,
  Huga
}

impl ToString for Foo {
    fn to_string(&self) -> String {
        serde_json::to_value(self)
            .unwrap()
            .as_str()
            .unwrap()
            .to_string()
    }
}

impl std::str::FromStr for Foo {
    type Err = serde_json::Error;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        serde_json::from_value(serde_json::Value::String(s.to_string()))
    }
}

HashMap<String, String> -> serde_json::map::Map<String, serde_json::Value>

let hashmap: HashMap<String, String> = IntoIter::new([]).collect();
let map = hashmap
  .into_iter()
  .map(|(k, v)| (k, serde_json::Value::String(v)))
  .collect::<serde_json::map::Map<_, _>>();

HashMap<String, serde_json::Value> -> serde_json::map::Map<String, serde_json::Value>

let hashmap: HashMap<String, String> = IntoIter::new([]).collect();
let map = hashmap
  .into_iter()
  .collect::<serde_json::map::Map<_, _>>();

HashMap<String, serde_json::Value> -> serde_json::Value::Object

let hashmap: HashMap<String, String> = IntoIter::new([]).collect();
let map = hashmap
  .into_iter()
  .map(|(k, v)| (k, serde_json::Value::String(v)))
  .collect::<serde_json::map::Map<_, _>>();
let obj = serde_json::Value::Object(map);

HashMap<String, String> -> impl serde::Deserialize

serde_aux を使うと string を int などに変換できる

use serde_aux::prelude::*;

#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
struct Obj{
  #[serde(deserialize_with = "deserialize_number_from_string")]
  id: i64,
  name: String,
}

let hashmap: HashMap<String, String> = IntoIter::new([]).collect();
let map = hashmap
  .into_iter()
  .map(|(k, v)| (k, serde_json::Value::String(v)))
  .collect::<serde_json::map::Map<_, _>>();
let obj = serde_json::Value::Object(map);
let o = serde_json::from_value::<Obj>(obj).unwrap();

serde_json::Value -> HashMap<String, serde_json::Value>

let json = serde_json::json!({});
let o = serde_json::from_value::<serde_json::map::Map<String, serde_json::Value>>(json)?;
let map = o.into_iter().collect::<HashMap<_, _>>();

serde_json::Value -> HashMap<String, String>

let json = serde_json::json!({});
let o = serde_json::from_value::<serde_json::map::Map<String, serde_json::Value>>(json)?;
let map = o.into_iter().map(|(k,v)| serde_json::from_value::<String>(v).map(|v|(k,v))).collect::<Result<HashMap<_, _>, _>>()?;

STDIN -> String

let mut buf = String::new();
std::io::stdin().read_line(&mut buf).unwrap();

[&T] -> [T] where T: Copy

let a = [1, 2, 3_u8];
let v_copied: Vec<_> = a.iter().copied().collect();
// let v_copied: Vec<_> = a.iter().map(|a| *a).collect(); // 同等

[T] -> [T] where T: Clone

let a = ["1".to_string(), "2".to_string(), "3".to_string()];
let v_copied: Vec<String> = a.iter().cloned().collect(); // 要素ごとに clone して Vec にする
// let v_copied: Vec<_> = a.iter().map(Clone::clone).collect(); // 同等
// let v_copied: Vec<_> = a.iter().map(|a| a.clone()).collect(); // 同等
// let v_copied: Vec<_> = a.clone().into_iter().collect(); // 最初に全部 clone する

Stream<Item=T> -> Vec<T>

use futures::prelude::stream::StreamExt;
let src = futures::stream::iter(vec![1, 2, 3]);
let vct = src.collect::<Vec<_>>().await;

RangeInclusive<T> -> futures::stream::Stream<Item=T>

let stream = futures::stream::iter(1..=3);

Stream<Item=Bytes> -> tokio::io::AsyncRead -> Stream<Item=String>

非同期バイトストリームから改行ごとの文字列ストリームに変換する頻出パターン

use futures::stream::StreamExt;
use tokio::io::AsyncBufReadExt;

let src: BoxStream<'static, bytes::Bytes> = futures::stream::empty().boxed();
let aread = tokio_util::io::StreamReader::new(src);
let stream: BoxStream<'static, String> = tokio_stream::wrappers::LinesStream::new(aread.lines()).boxed();

tokio::io::AsyncReadfutures::io::AsyncRead の違いについては以下を参照

AsyncReadStreamAsyncWriteSink の違いについては次のブログが詳しい

tokio::io::AsyncRead <-> futures::io::AsyncRead

tokio_util::compat を使う

use tokio_util::compat::FuturesAsyncReadCompatExt
let tokio_aread = fut_aread.compat();

use tokio_util::compat::TokioAsyncReadCompatExt;
let fut_aread = tokio_aread.compat();

tokio::io::AsyncWrite <-> futures::io::AsyncWrite

同上

use tokio_util::compat::FuturesAsyncWriteCompatExt
let tokio_aread = fut_aread.compat();

use tokio_util::compat::TokioAsyncWriteCompatExt;
let fut_aread = tokio_aread.compat();

futures::stream::StreamExt <-> tokio_stream::StreamExt

  • 型変換は存在しない
  • 大本の futures:stream::Stream trait を実装していればどちらも使用可能

DynamoDB AttributeValue JSON -> plain JSON

[dependencies]
rusoto_dynamodb = { version = "0.48.0", features = ["serialize_structs", "deserialize_structs"] }
serde = { version = "1.0.138", features = ["derive"] }
serde_dynamo = { version = "4.0.3", features = ["rusoto_dynamodb+0_48"] }
serde_json = "1.0.82"
fn dynamodb_item_to_json(
    item: std::collections::hash_map::HashMap<String, rusoto_dynamodb::AttributeValue>,
) -> Result<serde_json::Value, serde_dynamo::Error> {
    let o = item
        .into_iter()
        .map(|(k, v)| {
            serde_dynamo::from_attribute_value::<rusoto_dynamodb::AttributeValue, serde_json::Value>(v)
                .map(|v| (k, v))
        })
        .collect::<Result<Vec<_>, serde_dynamo::Error>>()?;
    let o = o.into_iter().collect::<serde_json::map::Map<String, serde_json::Value>>();
    Ok(serde_json::Value::from(o))
}

fn main() {
    let o = r#"{"hash_key":{"S":"hoge"}}"#;
    let o = serde_json::from_str::<serde_json::Value>(o).unwrap();
    let o = serde_json::json!({"Item": o});
    let o = serde_json::from_value::<rusoto_dynamodb::GetItemOutput>(o).unwrap().item.unwrap();
    let o = dynamodb_item_to_json(o).unwrap();
    let o = serde_json::to_string(&o).unwrap();
    assert_eq!(o, r#"{"hash_key":"hoge"}"#);
}

*mut c_char -> &Cstr -> &str

use std::os::raw::c_char;
use std::ffi::CStr;

let char_ptr: *mut c_char;
let cstr: &CStr = CStr::from_ptr(char_ptr);
let str_: &str = cstr.to_str().unwrap();

&str -> CString -> *mut c_char

use std::ffi::CString;
use std::os::raw::c_char;

let str_: &str = "hoge";
let c_string: CString = CString::new(str_).unwrap();
let c_string_ptr: *const c_char = c_string.as_ptr();

*const u8 -> &[u8]

let a: *const u8 = std::ptr::null::<u8>();
let b: &u8 = unsafe{ &*a };
let c: &[u8] = std::slice::from_ref(b);

ヒープを使わず !Unpin なstack 変数を使った自己参照構造体の作り方

#[derive(Debug)]
struct A<'a> {
    a: [u8; 32],
    a_ref: std::pin::Pin<&'a [u8]>,
}
fn main() {
    let mut a = std::pin::pin!(A {
        a: [0; 32],
        #[allow(deref_nullptr)]
        a_ref: std::pin::Pin::new(std::slice::from_ref(unsafe{ &*std::ptr::null::<u8>() })),
    });
    a.a_ref = std::pin::Pin::new(&a.a);
    // dbg!(a); // cannot move out of `a` because it is borrowed
    dbg!(&a);
}
163
108
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
163
108