Ruby Ruby 开发遇到的坑

rocLv · 2015年10月23日 · 最后由 lzding 回复于 2015年11月19日 · 4879 次阅读

写在前面

我希望这篇帖子能作为大家 Ruby 开发注意事项的一个总结,欢迎大家跟帖,说说我们自己在 Ruby 开发中遇到的坑

最近在看Liquid源码时看到一句

 def render(*args)
      return ''.freeze if @root.nil?
      ...
end

所以我就看了看freeze方法的定义。 它是 Object 的实例方法,所以基本上所有的 Ruby 对象都继承了 Object 的 freeze 方法。 为了进一步搞明白作者写 ‘’.freeze的意图,我就在命令行试了试 freeze

# ruby 2.2.3
 > a = ''
 => "" 
> a.freeze
 => "" 
 > a << 'b'
RuntimeError: can't modify frozen String
    from (irb):3
    from /home/i/.rvm/rubies/ruby-2.2.3/bin/irb:11:in `<main>'
> a += 'c'
 => "c" 
 > a
 => "c" 
 > a.freeze
 => "c" 
 > a << 'b'
RuntimeError: can't modify frozen String
    from (irb):7
    from /home/i/.rvm/rubies/ruby-2.2.3/bin/irb:11:in `<main>'
 > a += 'd'
 => "cd" 

freeze只是禁止了<<操作,但是对于 =、+却可以,而且一旦使用了运算符,或者说调用了+、=方法,变量又从frozen状态回到了正常状态,

 > a.freeze
 => "cd" 
 > a << 'e'
RuntimeError: can't modify frozen String
    from (irb):12
    from /home/i/.rvm/rubies/ruby-2.2.3/bin/irb:11:in `<main>'
 > a.frozen?
 => true 
> a += 'e'
 => "cde" 
 > a.frozen?
 => false 

官方文档是这样说的

There is no way to unfreeze a frozen object. See also Object#frozen?.

但是我们做到了 😄

对于 Fixnum, Bignum, Float, Symbol 来说永远是 Frozen 状态。

Objects of the following classes are always frozen: Fixnum, Bignum, Float, Symbol.

我又试了下数组

 > a = [1]
 => [1] 
 > a.freeze
 => [1] 
 > a << 2
RuntimeError: can't modify frozen Array
    from (irb):44
    from /home/i/.rvm/rubies/ruby-2.2.3/bin/irb:11:in `<main>'
 > a += [2]
 => [1, 2] 
 > a
 => [1, 2] 
 > a.frozen?
 => false 
 > a.freeze
 => [1, 2] 
 > a -= [2]
 => [1] 
 > a.frozen?
 => false 

所以,对于数组来说,也并没有真正的做到 freeze。 对于frozen的对象来说,只是禁止了<<操作符而已。不知道是 bug 还是故意设计的。

虽然,这个方法用的相对比较少,但是记录在这里,希望起到抛砖引玉的作用。

----------------------------------------------------2015.10.25---------------------------------------- 补充: 昨晚睡觉的时候,突然想起来应该看看 ruby 源码

今天起来一看,果然+操作符返回的是一个新的字符串,而<<concat的别名,返回对象本身。 所以freeze是对对象本身的操作。

a.freeze 冻结了 a 指向的对象,<< 直接修改对象,操作被阻止。+=a 指向新的对象,操作没有被阻止。

irb(main):001:0> a = 'c'
=> "c"
irb(main):002:0> a.object_id
=> 18017940
irb(main):003:0> a << 'd'
=> "cd"
irb(main):004:0> a.object_id
=> 18017940
irb(main):005:0> a.freeze
=> "cd"
irb(main):006:0> a << 'e'
RuntimeError: can't modify frozen String
        from (irb):6
        from /usr/bin/irb:11:in `<main>'
irb(main):007:0> a += 'e'
=> "cde"
irb(main):008:0> a.object_id
=> 17868100

不太明白这个"".freeze在这里起什么作用。

#1 楼 @rei 在编程实践中,这样做有什么意义?是为了 GC? 我看到他源码了还有这种用法: Lexer

SPECIALS = {
      '|'.freeze => :pipe,
      '.'.freeze => :dot,
      ':'.freeze => :colon,
      ','.freeze => :comma,
      '['.freeze => :open_square,
      ']'.freeze => :close_square,
      '('.freeze => :open_round,
      ')'.freeze => :close_round,
      '?'.freeze => :question,
      '-'.freeze => :dash
    }

#2 楼 @billy 可能是性能,前阵子 Rails 的一个性能优化就是把很多 String freeze 了,因为这个原因 Ruby 3.0 可能会把 String 默认设为 Immutable https://bugs.ruby-lang.org/issues/11473

@rei 明白了,多谢!

#4 楼 @rei 在吸收新的语言特性方面,Swift 真的做了不少大胆的尝试。如果 String 变成 Immutable 了,会不会把 Symbol 变成鸡肋?

#7 楼 @roclv matz 好像说了这个问题,可以看视频

freeze 方法冻结了变量名背后代表的对象(object),而不是变量名(variable identifier)本身。

这样理解的对吗?

rebinding 和 modification 之间的区别

Symbol 应该会淘汰。 不要为了性能而改变语法。

#12 楼 @sevk BigDecimal 会淘汰 Integer 和 Float 吗?

#13 楼 @rei 5 年后应该会,哈哈

freeze 冻结的是一块内存,这么理解对吗?

需要 登录 后方可回复, 如果你还没有账号请 注册新账号