Bug #6195

String#[] に逆順の Range を渡した場合の挙動

Added by Kenta Murata over 3 years ago. Updated about 3 years ago.

[ruby-dev:45429]
Status:Rejected
Priority:Normal
Assignee:Yukihiro Matsumoto
ruby -v:ruby 2.0.0dev (2012-03-23 trunk 35121) [x86_64-darwin11.3.0] Backport:

Description

以下のように String#[] に対して、範囲の開始インデックスが文字列の長さ以下の値である逆順の Range (beg > end) を渡した場合に空文字列が返ります。

"1"[1..0] #=> ""
"1"[1..-1] #=> ""
"123"[2..1] #=> ""
"123"[2..-2] #=> ""

一方、範囲の開始インデックスが文字列の長さより大きい場合は nil が返ります。

"1"[2..0] #=> nil
"1"[2..-1] #=> nil
"123"[4..1] #=> nil
"123"[4..-2] #=> nil

この挙動の違いは、rb_range_beg_len の実装に起因しています。
文字列のインデックスが昇順のみである仕様を考慮すると、上記前者の場合も nil が返るべきじゃないかと思います。

History

#1 Updated by Shugo Maeda over 3 years ago

前田です。

mrkn (Kenta Murata) wrote:

以下のように String#[] に対して、範囲の開始インデックスが文字列の長さ以下の値である逆順の Range (beg > end) を渡した場合に空文字列が返ります。

"1"[1..0] #=> ""
"1"[1..-1] #=> ""
"123"[2..1] #=> ""
"123"[2..-2] #=> ""

一方、範囲の開始インデックスが文字列の長さより大きい場合は nil が返ります。

"1"[2..0] #=> nil
"1"[2..-1] #=> nil
"123"[4..1] #=> nil
"123"[4..-2] #=> nil

この挙動の違いは、rb_range_beg_len の実装に起因しています。
文字列のインデックスが昇順のみである仕様を考慮すると、上記前者の場合も nil が返るべきじゃないかと思います。

私も最初そう思ったのですが、PythonやJavaScriptではどちらの場合も空文字列を返すようです。

以下の例はどちらもRubyのs[x...y]相当(s[x..y]ではない)です。

defiant:ruby$ python
Python 2.7.2+ (default, Oct 4 2011, 20:03:08)
[GCC 4.6.1] on linux2
Type "help", "copyright", "credits" or "license" for more information.

"abc"
'b'
"abc"
''
"abc"
''
"abc"
''
defiant:ruby$ js
"abc".slice(1,2)
'b'
"abc".slice(1,1)
''
"abc".slice(1,0)
''
"abc".slice(4,0)
''

これはこれで合理的だと思いますがどうでしょうか。

また、Net::FTP#parse227が今の挙動に依存しているようです。

def parse227(resp) # :nodoc:
  if resp[0, 3] != "227"
    raise FTPReplyError, resp
  end
  left = resp.index("(")
  right = resp.index(")")
  if left == nil or right == nil
    raise FTPProtoError, resp
  end
  numbers = resp[left + 1 .. right - 1].split(",")
  if numbers.length != 6
    raise FTPProtoError, resp
  end
  host = numbers[0, 4].join(".")
  port = (numbers[4].to_i << 8) + numbers[5].to_i
  return host, port
end

respが"227 )("のようなケースでも今のString#[]ではresp[left + 1 .. right - 1]
が""になるのでsplitできますが、nilを返すようになるとnil.split(",")でNoMethodError
になってしまいます。
このコードがひどいと言われればそれまでですが、若気の至りということで。

たぶん当時のftplib.pyのパクリなんだろうなあ。

#2 Updated by Yusuke Endoh over 3 years ago

  • Status changed from Open to Assigned

#3 Updated by Akinori MUSHA over 3 years ago

文字列のスライスが(Rangeの始点さえ範囲内なら)nilを返さないという現仕様は利便性にフォーカスしていて合理的だと思っています。

たとえば、

if s.start_with?(prefix)
  logger.info "message body is " + s[prefix.length..-1]
end

のようなコードは私の手元にもたくさんありますし、Rubyのソースツリーで grep -rF ..-1 lib して眺めただけでもnilを期待していない所はいくつもあるようです。

#4 Updated by Yukihiro Matsumoto about 3 years ago

  • Status changed from Assigned to Rejected

本人も取り下げたようなのでリジェクトします。

Matz.

Also available in: Atom PDF