🍙

Ruby脳のためのRust文字列系メソッドまとめ

2022/03/25に公開
Ruby Rust
bytesize len
[] get
chars chars
size chars.count
to_i parse::<isize>()
lines(chomp: true) lines
strip trim
lstrip trim_start
rstrip trim_end
split split_whitespace
split(x) split(x)
gsub!(str, "") remove_matches(str)
concat(str) push_str(str)
concat(ch) push(ch)
self * n repeat(n)
clear clear
empty? is_empty
include? contains
chars.each.with_index char_indices
inspect escape_debug
gsub replace
gsub 最初のn回だけ replacen
insert(i, str) insert_str(i, str)
insert(i, ch) insert(i, ch)
new new
bytes into_bytes
bytes as_bytes
bytes as_mut_vec
slice!(n..) truncate(n)
slice!(-1) pop
slice!(i) remove(i)
select! 類似 retain
slice!(i..) split_off(i)
[range] = v replace_range(range, v)
slice get_mut
split(x).reverse rsplit(x)
split(/(?<=x)/) split_inclusive(x)
split(x) split_terminator(x)
split(x).reverse rsplit_terminator(x)
split(sep, n) splitn(n, sep)
split 末尾から rsplitn(n, sep)
split(sep, 2) split_once(sep)
split(sep, 2) 末尾から rsplit_once(sep)
scan matches
reverse.scan rmatches
start_with? starts_with
end_with? ends_with
ascii_only? is_ascii
index find
rindex rfind
strip 類似 trim_matches
lstrip 類似 trim_start_matches
rstrip 類似 trim_end_matches
delete_prefix strip_prefix
delete_suffix strip_suffix
casecmp?(other) eq_ignore_ascii_case
upcase to_uppercase
downcase to_lowercase
tr("a-z", "A-Z") to_ascii_uppercase
tr("A-Z", "a-z") to_ascii_lowercase
tr!("a-z", "A-Z") make_ascii_uppercase
tr!("A-Z", "a-z") make_ascii_lowercase
? match_indices
? rmatch_indices
dump 一部unicode escape_default
dump 全部unicode escape_unicode
to_s to_string
to_s as_str
to_s 更新用 as_mut_str
ary.pack("C*") from_utf8(ary)
ary.pack("C*").scrub from_utf8_lossy(ary)
new(capacity: xxx) with_capacity(xxx)
? reserve(size)
? shrink_to_fit
? into_raw_parts
? from_raw_parts
? into_boxed_str
? is_char_boundary
? as_ptr
? as_mut_ptr

bytesizelen

Ruby
"🥑".bytesize  # => 4
Rust
"🥑".len()  // => 4

Ruby の配列は文字単位になっているのに対して Rust はバイト単位の配列になっている

[]get

Ruby
s = "A🥑B"
s[0] # => "A"
s[1] # => "🥑"
s[2] # => "B"
Rust
let s = "A🥑B";
s.get(0..1) // => Some("A")
s.get(1..5) // => Some("🥑")
s.get(5..6) // => Some("B")
  • 範囲でしか指定できない
  • Rustではマルチバイト文字を含む文字列の操作が難しそうだ

charschars

Ruby
"Aあ🥑".chars       # => ["A", "あ", "🥑"]
"Aあ🥑".codepoints  # => [65, 12354, 129361]
Rust
"Aあ🥑".chars().collect::<Vec<_>>() // => ['A', 'あ', '🥑']

文字単位で扱う場合はさっさとこれで配列化してから操作した方がよさそう

sizechars.count

Ruby
"🥑".size  # => 1
Rust
"🥑".chars().count()  // => 1

文字数

to_iparse::<isize>()

Ruby
"567".to_i # => 567
Rust
"567".parse::<isize>()          // => Ok(567)

let v: isize = "567".parse().unwrap();
v // => 567

" 567 ".parse::<isize>()        // => Err(ParseIntError { kind: InvalidDigit })
" 567 ".trim().parse::<isize>() // => Ok(567)
  • ::<xxx> の部分をターボフィッシュというらしい
    • 戻値を受け取る変数に型指定があれば省略できる
    • parse の方に指定した方がわかりやすい
  • 空白や改行が含まれているだけで InvalidDigit と言われる
    • trim() とペアで使おう

lines(chomp: true)lines

Ruby
"foo\nbar\nbaz\n".lines(chomp: true) # => ["foo", "bar", "baz"]
Rust
"foo\nbar\nbaz\n".lines().collect::<Vec<_>>() // => ["foo", "bar", "baz"]

striptrim

Ruby
" foo ".strip # => "foo"
Rust
" foo ".trim() // => "foo"

lstriptrim_start

Ruby
" foo ".lstrip # => "foo "
Rust
" foo ".trim_start() // => "foo "

rstriptrim_end

Ruby
" foo ".rstrip # => " foo"
Rust
" foo ".trim_end() // => " foo"

splitsplit_whitespace

Ruby
" a\r\n b c\n".gsub(/[[:space:]]+/, " ").split # => ["a", "b", "c"]
Rust
" a\r\n b c\n".split_whitespace().collect::<Vec<_>>() // => ["a", "b", "c"]
  • 全角スペースもスペースとして扱う

split(x)split(x)

Ruby
"a-b-c".split("-")    # => ["a", "b", "c"]
"a-b_c".split(/[-_]/) # => ["a", "b", "c"]
Rust
"a-b-c".split('-').collect::<Vec<_>>()          // => ["a", "b", "c"]
"a-b-c".split(|e| e == '-').collect::<Vec<_>>() // => ["a", "b", "c"]
"a-b_c".split(['-', '_']).collect::<Vec<_>>()   // => ["a", "b", "c"]

gsub!(str, "")remove_matches(str)

Ruby
s = "foo_bar"
s.gsub!("bar", "")
s # => "foo_"
Rust (nightly)
let mut s = String::from("foo_bar");
s.remove_matches("bar");
s // => "foo_"

concat(str)push_str(str)

Ruby
s = "foo"
s.concat("bar")
s  # => "foobar"
Rust
let mut s = String::from("foo");
s.push_str("bar");
s  // => "foobar"

メソッド名がイケてない

concat(ch)push(ch)

Ruby
s = "foo"
s.concat('b')
s.concat('a')
s.concat('r')
s  # => "foobar"
Rust
let mut s = String::from("foo");
s.push('b');
s.push('a');
s.push('r');
s  // => "foobar"

こんな滅多に使わなそうなのは push_char にして push_str を push にしてほしかった

self * nrepeat(n)

Ruby
"foo" * 2 # => "foofoo"
Rust
"foo".repeat(2) // => "foofoo"

clearclear

Ruby
s = "foo"
s.clear
s  # => ""
Rust
let mut s = String::from("foo");
s.clear();
s  // => ""

empty?is_empty

Ruby
"".empty?  # => true
Rust
"".is_empty()  // => true

include?contains

Ruby
"abcd".include?("bc") # => true
Rust
"abcd".contains("bc") // => true

chars.each.with_indexchar_indices

Ruby
"Aあ🥑".chars.each.with_index.entries # => [["A", 0], ["あ", 1], ["🥑", 2]]
Rust
"Aあ🥑".char_indices().collect::<Vec<_>>() // => [(0, 'A'), (1, 'あ'), (4, '🥑')]
  • Rust のほうの位置は連番ではない
  • バイト配列での位置になっている

inspectescape_debug

Ruby
"A🥑\n".inspect  # => "\"A🥑\\n\""
Rust
"A🥑\n".escape_debug().to_string()    // => "A🥑\\n"

改行がエスケープされ \n 表記になる

gsubreplace

Ruby
"abcabcabc".gsub("ab", "__") # => "__c__c__c"
Rust
"abcabcabc".replace("ab", "__") // => "__c__c__c"

gsub 最初のn回だけreplacen

Ruby
# もっとましな方法ありそう
n = 2
c = 0
s = "abcabcabc".gsub("ab") do |m|
  c += 1
  if c <= n
    "__"
  else
    m
  end
end
s # => "__c__cabc"
Rust
"abcabcabc".replacen("ab", "__", 2) // => "__c__cabc"

insert(i, str)insert_str(i, str)

Ruby
s = "foo"
s.insert(1, "__")
s # => "f__oo"
Rust
let mut s = String::from("foo");
s.insert_str(1, "__"); // 文字列
s // => "f__oo"

メソッド名がイケてない

insert(i, ch)insert(i, ch)

Ruby
s = "foo"
s.insert(1, "_")
s # => "f_oo"
Rust
let mut s = String::from("foo");
s.insert(1, '_'); // 文字
s // => "f_oo"

newnew

Ruby
s = String.new
s  # => ""
Rust
let s = String::new();
s  // => ""

bytesinto_bytes

Ruby
"abc".bytes # => [97, 98, 99]
Rust
String::from("abc").into_bytes() // => [97, 98, 99]

所有権が移動するやつ?

bytesas_bytes

Ruby
"abc".bytes # => [97, 98, 99]
Rust
"abc".as_bytes() // => [97, 98, 99]
b"abc"           // => [97, 98, 99]

型変換? 表記がアレだけど b をつけても同じ結果になる

bytesas_mut_vec

Ruby
"abc".bytes # => [97, 98, 99]
Rust
let mut s = String::from("abc");
let v = unsafe { s.as_mut_vec() };
v // => [97, 98, 99]

同じようなのがいくつもあるな

slice!(n..)truncate(n)

Ruby
s = "foobar"
s.slice!(3..) # => "bar"
s             # => "foo"

s = "A🥑B"
s.slice!(2..) # => "B"
s             # => "A🥑"
Rust
// truncate した部分を返したりはしない
let mut s = String::from("foobar");
s.truncate(3) // => ()
s             // => "foo"

// パニックになる
// let mut s = String::from("A🥑B");
// s.truncate(2)

マルチバイト文字が含まれていたら文字の境界線を慎重に指定しないとパニックになる

slice!(-1)pop

Ruby
s = "foobar"
s.slice!(-1) # => "r"
s            # => "fooba"
Rust
let mut s = String::from("foobar");
s.pop() // => Some('r')
s       // => "fooba"

slice!(i)remove(i)

Ruby
s = "foobar"
s.slice!(3) # => "b"
s           # => "fooar"
Rust
let mut s = String::from("foobar");
s.remove(3) // => 'b'
s           // => "fooar"

select! 類似retain

Ruby
class String
  def retain(&block)
    replace(each_char.select(&block).join)
  end
end

s = "f_o_o"
s.retain { |e| e != "_" }
s # => "foo"

s = "f_o_o"
s.delete!("_")
s # => "foo"
Rust
let mut s = String::from("f_o_o");
s.retain(|e| e != '_'); // "_" では文字列を表すのでコンパイルエラー
s // => "foo"

この例では remove_matches("_") のほうが良い

slice!(i..)split_off(i)

Ruby
s = "foo"
s.slice!(1..)  # => "oo"
s              # => "f"

s = "foo"
s.byteslice(1...)            # => "oo"
s.replace(s.byteslice(...1))
s                            # => "f"
Rust
let mut s = String::from("foo");
s.split_off(1) // => "oo"
s              // => "f"

正確には byteslice の破壊版に近い

[range] = vreplace_range(range, v)

Ruby
s = "abcd"
s[...2] = "__"
s  # => "__cd"
Rust
let mut s = String::from("abcd");
s.replace_range(..2, "__");
s  // => "__cd"

sliceget_mut

Ruby
"foo".slice(0..1).upcase # => "FO"
Rust
let mut s = String::from("foo");
let s = s.get_mut(0..=1);
let s = s.map(|e| {         // e は "fo"
    e.make_ascii_uppercase();
    &*e
});
s // => Some("FO")

なんだこれ

split(x).reversersplit(x)

Ruby
"a-b-c".split("-").reverse # => ["c", "b", "a"]
Rust
"a-b-c".rsplit('-').collect::<Vec<_>>()  // => ["c", "b", "a"]

split(/(?<=x)/)split_inclusive(x)

Ruby
"a-b-c".split(/(?<=-)/)  # => ["a-", "b-", "c"]
Rust
"a-b-c".split_inclusive('-').collect::<Vec<_>>() // => ["a-", "b-", "c"]

セパレータで分けて前の要素に残す

split(x)split_terminator(x)

Ruby
"a-b-c-".split("-") # => ["a", "b", "c"]
Rust
"a-b-c-".split_terminator("-").collect::<Vec<_>>() // => ["a", "b", "c"]

セパレータではなく句点のように終端に特定の文字がある構造を分ける

split(x).reversersplit_terminator(x)

Ruby
"a-b-c-".split("-").reverse # => ["c", "b", "a"]
Rust
"a-b-c-".rsplit_terminator("-").collect::<Vec<_>>() // => ["c", "b", "a"]

split(sep, n)splitn(n, sep)

Ruby
"foo-bar-baz".split("-", 2) # => ["foo", "bar-baz"]
Rust
"foo-bar-baz".splitn(2, "-").collect::<Vec<_>>() // => ["foo", "bar-baz"]

split 末尾からrsplitn(n, sep)

Ruby
"foo-bar-baz".reverse.split("-", 2).collect(&:reverse) # => ["baz", "foo-bar"]
Rust
"foo-bar-baz".rsplitn(2, "-").collect::<Vec<_>>() // => ["baz", "foo-bar"]

split(sep, 2)split_once(sep)

Ruby
"foo-bar-baz".split("-", 2) # => ["foo", "bar-baz"]
Rust
"foo-bar-baz".split_once("-") // => Some(("foo", "bar-baz"))

split(sep, 2) 末尾からrsplit_once(sep)

Ruby
s = "foo-bar-baz".reverse.split("-", 2)
s.collect(&:reverse).reverse # => ["foo-bar", "baz"]
Rust
"foo-bar-baz".rsplit_once("-") // => Some(("foo-bar", "baz"))

scanmatches

Ruby
"_56_".scan(/\d/) # => ["5", "6"]
Rust
"_56_".matches(char::is_numeric).collect::<Vec<_>>() // => ["5", "6"]

予想に反して1文字づつ返ってくる

reverse.scanrmatches

Ruby
"_56_".reverse.scan(/\d/) # => ["6", "5"]
Rust
"_56_".rmatches(char::is_numeric).collect::<Vec<_>>() // => ["6", "5"]

start_with?starts_with

Ruby
"abcd".start_with?("ab") # => true
Rust
"abcd".starts_with("ab") // => true

end_with?ends_with

Ruby
"abcd".end_with?("cd") # => true
Rust
"abcd".ends_with("cd") // => true

ascii_only?is_ascii

Ruby
"foo".ascii_only? # => true
Rust
"foo".is_ascii() // => true

indexfind

Ruby
s = "abcdabcd"
s.index("c")                   # => 2
s.index("cd")                  # => 2
s.chars.index { |e| e == 'c' } # => 2
Rust
let s = "abcdabcd";
s.find('c')           // => Some(2)
s.find("cd")          // => Some(2)
s.find(|e| e == 'c')  // => Some(2)

xxx, xxx_by, xxx_by_key シリーズのように引数の型が変わるたびに異なるメソッドになるのが Rust 流かと思いきや、このメソッドはいろんな引数に対応していてありがたい

rindexrfind

Ruby
s = "abcdabcd"
s.rindex("c")                   # => 6
s.rindex("cd")                  # => 6
s.chars.rindex { |e| e == 'c' } # => 6
Rust
let s = "abcdabcd";
s.rfind('c')           // => Some(6)
s.rfind("cd")          // => Some(6)
s.rfind(|e| e == 'c')  // => Some(6)

strip 類似trim_matches

Ruby
"56foo78".sub(/\A\d+(.*?)\d*\z/, '\1') # => "foo"
Rust
"56foo78".trim_matches(char::is_numeric) // => "foo"

lstrip 類似trim_start_matches

Ruby
"56foo78".sub(/\A\d+/, "") # => "foo78"
Rust
"56foo78".trim_start_matches(char::is_numeric) // => "foo78"

rstrip 類似trim_end_matches

Ruby
"56foo78".sub(/\d+\z/, "") # => "56foo"
Rust
"56foo78".trim_end_matches(char::is_numeric) // => "56foo"

delete_prefixstrip_prefix

Ruby
"56foo56".delete_prefix("56") # => "foo56"
Rust
"56foo56".strip_prefix("56") // => Some("foo56")

delete_suffixstrip_suffix

Ruby
"56foo56".delete_suffix("56") # => "56foo"
Rust
"56foo56".strip_suffix("56") // => Some("56foo")

casecmp?(other)eq_ignore_ascii_case

Ruby
"Föö".casecmp?("föö") # => true
Rust
"Föö".eq_ignore_ascii_case("föö") // => true

upcaseto_uppercase

Ruby
"aAöÖ❤".upcase # => "AAÖÖ❤"
Rust
"aAöÖ❤".to_uppercase() // => "AAÖÖ❤"

downcaseto_lowercase

Ruby
"aAöÖ❤".downcase # => "aaöö❤"
Rust
"aAöÖ❤".to_lowercase() // => "aaöö❤"

tr("a-z", "A-Z")to_ascii_uppercase

Ruby
"aAöÖ❤".tr("a-z", "A-Z") # => "AAöÖ❤"
Rust
"aAöÖ❤".to_ascii_uppercase() // => "AAöÖ❤"

tr("A-Z", "a-z")to_ascii_lowercase

Ruby
"aAöÖ❤".tr("A-Z", "a-z") # => "aaöÖ❤"
Rust
"aAöÖ❤".to_ascii_lowercase() // => "aaöÖ❤"

tr!("a-z", "A-Z")make_ascii_uppercase

Ruby
s = "aAöÖ❤"
s.tr!("a-z", "A-Z")
s # => "AAöÖ❤"
Rust
let mut s = String::from("aAöÖ❤");
s.make_ascii_uppercase();
s // => "AAöÖ❤"

tr!("A-Z", "a-z")make_ascii_lowercase

Ruby
s = "aAöÖ❤"
s.tr!("A-Z", "a-z")
s # => "aaöÖ❤"
Rust
let mut s = String::from("aAöÖ❤");
s.make_ascii_lowercase();
s // => "aaöÖ❤"

?match_indices

Ruby
"_56_".chars.filter_map.with_index { |e, i| [i, e] if e.match?(/\d/) } # => [[1, "5"], [2, "6"]]
Rust
"_56_".match_indices(char::is_numeric).collect::<Vec<_>>() // => [(1, "5"), (2, "6")]

?rmatch_indices

Ruby
"_56_".chars.filter_map.with_index { |e, i| [i, e] if e.match?(/\d/) }.reverse # => [[2, "6"], [1, "5"]]
Rust
"_56_".rmatch_indices(char::is_numeric).collect::<Vec<_>>() // => [(2, "6"), (1, "5")]

dump 一部unicodeescape_default

Ruby
"A🥑\n".dump  # => "\"A\\u{1F951}\\n\""
Rust
"A🥑\n".escape_default().to_string()    // => "A\\u{1f951}\\n"
  • 絵文字はユニコードの数字表記になる
  • ちょうど良い

dump 全部unicodeescape_unicode

Ruby
"A🥑\n".dump  # => "\"A\\u{1F951}\\n\""
Rust
"A🥑\n".escape_unicode().to_string()    // => "\\u{41}\\u{1f951}\\u{a}"
  • 全部ユニコードの数字表記になる
  • 改行も a になって余計わからん

to_sto_string

Rust
"foo".to_string() // => "foo"

to_sas_str

Ruby
"abc".to_s # => "abc"
Rust
String::from("abc").as_str() // => "abc"
  • String型にしか生えてない
  • to_string と何が違う?

to_s 更新用as_mut_str

Ruby
"abc".to_s # => "abc"
Rust
let mut x = String::from("abc");
let s = x.as_mut_str(); // let mut と書かなくていいのかな?
s.make_ascii_uppercase();
s // => "ABC"

// これと同じだけど、どういうこと???
let mut s = String::from("abc");
s.make_ascii_uppercase();
s // => "ABC"

なんかよくわからないけどそのうちわかるだろう

ary.pack("C*")from_utf8(ary)

Ruby
puts [240, 159, 144, 178].pack("C*")
# >> 🐲
Rust
String::from_utf8(vec![240, 159, 144, 178]) // => Ok("🐲")

ary.pack("C*").scrubfrom_utf8_lossy(ary)

Rust
let v = vec![102, 111, 111, 129, 98, 97, 114];
String::from_utf8_lossy(&v) // => "foo�bar"

129 は無効な文字なので � になる

new(capacity: xxx)with_capacity(xxx)

Ruby
s = String.new(capacity: 65535)
s  # => ""
# capacity 見れんの?
Rust
let s = String::with_capacity(65535);
s            // => ""
s.capacity() // => 65535

?reserve(size)

Rust
let mut s = String::from("foo");
s.capacity() // => 3
s.reserve(65535);
s.capacity() // => 65538
  • あとから容量を確保する
  • キリの良い単位で確保するので指定よりも多く確保することもある

?shrink_to_fit

Rust
let mut s = String::from("foo");
s.capacity() // => 3
s.reserve(65535);
s.capacity() // => 65538
s.shrink_to_fit();
s.capacity() // => 3

reserve の逆でメモリを切り詰める

?into_raw_parts

Rust (nightly)
let s = String::from("foo");
let (ptr, len, cap) = s.into_raw_parts();
ptr // => 0x7feb59405eb0
len // => 3
cap // => 3

文字列をさらにラップしているらしい

?from_raw_parts

Rust
use std::mem;
let s = String::from("foo");
let mut s = mem::ManuallyDrop::new(s);
s // => ManuallyDrop { value: "foo" }
let ptr = s.as_mut_ptr();
let len = s.len();
let cap = s.capacity();
ptr // => 0x7f80f4c05eb0
len // => 3
cap // => 3
unsafe {
    let s = String::from_raw_parts(ptr, len, cap);
    s // => "foo"
}

自力で文字列作成用?

?into_boxed_str

Rust
let s = String::from("foo");
let x = s.into_boxed_str(); // s から x に所有権移動しちゃってる
x // => "foo"

どゆこと?

?is_char_boundary

Rust
let s = "A🥑B";
s.is_char_boundary(0)  // => true
s.is_char_boundary(1)  // => true
s.is_char_boundary(2)  // => false
s.is_char_boundary(3)  // => false
s.is_char_boundary(4)  // => false
s.is_char_boundary(5)  // => true
s.is_char_boundary(6)  // => true

指定位置のバイトがUTF-8コードポイントシーケンスの最初のバイトまたは文字列の終わりなら true らしいがピンとこない

?as_ptr

Rust
let s = "foo";
s.as_ptr() // => 0x1020434a0

ptr はC言語で言うところポインタなのかな

?as_mut_ptr

Rust
let mut s = String::from("foo");
s.as_mut_ptr() // => 0x7ff0f0405eb0

文字を更新するとき用

Discussion