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
- https://docs.rs/bytes/1.1.0/bytes/struct.Bytes.html
- https://stackoverflow.com/questions/53841510/how-can-i-convert-a-bytesbytes-to-a-str-without-making-any-copies
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 の ToPrimitive
と FromPrimitive
を使います
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
f64
-> i64
let a: f64 = 0.1;
let b: i64 = a.round() as i64;
任意の 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();
- https://docs.rs/chrono/0.4.19/chrono/offset/trait.TimeZone.html?search=#method.timestamp_millis_opt
- https://qiita.com/fujitayy/items/ae6175118cbed7134594#unix-time%E3%81%8B%E3%82%89%E3%81%AE%E5%A4%89%E6%8F%9B
DateTime<UTC>
-> unixtimestamp(millis)
let timestamp: i64 = chrono::Utc::now().timestamp_millis();
- https://docs.rs/chrono/0.4.19/chrono/struct.DateTime.html#method.timestamp_millis
- https://qiita.com/fujitayy/items/ae6175118cbed7134594#unix-time%E3%81%B8%E3%81%AE%E5%A4%89%E6%8F%9B
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();
- https://docs.rs/tokio/1.24.2/tokio/io/trait.AsyncRead.html
- https://docs.rs/tokio/1.24.2/tokio/io/trait.AsyncBufReadExt.html
- https://docs.rs/tokio-stream/0.1.11/tokio_stream/wrappers/struct.LinesStream.html
- https://docs.rs/tokio-util/0.7.4/tokio_util/io/struct.StreamReader.html
tokio::io::AsyncRead
と futures::io::AsyncRead
の違いについては以下を参照
- https://smallcultfollowing.com/babysteps/blog/2019/12/10/async-interview-2-cramertj-part-2/
- https://github.com/tokio-rs/tokio/pull/1744
AsyncRead
と Stream
、AsyncWrite
と Sink
の違いについては次のブログが詳しい
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
-
{"hash_key":{"S":"hoge"}}
を{"hash_key":"hoge"}
にしたいとき -
rusoto_dynamodb
のdeserialize_structs
features とserde_dynamo
を使う - 一旦
rusoto_dynamodb::GetItemOutput
でデシリアライズしておいてHashMap<String, rusoto_dynamodb::AttributeValue>
を得てからserde_dynamo::from_attribute_value
で JSON に変換していく - B や BS などには対応してないので注意
- aws-sdk-rust では未対応の模様
[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);
}
Vec<impl Into<JsValue>>
-> Array
let arr = vec![0, 1]
.into_iter()
.map(js_sys::JsValue::from)
.collect::<js_sys::Array>();