SafeClone
This tiny gem implements a version of clone called safe_clone. In Ruby, if an attempt is made to clone an immutable data item like a number, an error occurs. The justification for this uncharacteristic strictness is not at all clear, but it does mean that the clone operation must be applied with great care.
Unlike the standard clone method, the safe_clone method does not throw an exception when it sees un-clonable value objects like 42 or true. These values simply return themselves. This is deemed correct because those types of objects are immutable and do not need to be duped. Instead of raising an exception, the code returns the immutable object instead.
On a note about performance, this gem does not just rescue the exceptions normally generated by clone, it prevents them from occurring and wasting time in the first place.
Finally, this gem does not monkey patch the behavior of the clone method. Modifying such a crucial method was considered too risky. Instead, the safe_clone method is introduced. This is done to reduce the possibility of breaking existing code that often occurs when monkey patching goes too far.
Family Overview
This gem is a member of a family of four gems that all provide data copying services in a safe, easy to use format. The following outlines the available gems and how to chose from among them.
Depth / Action | Need to copy all. | Need to copy data only. |
---|---|---|
Need a shallow copy | require 'safe_clone' | require 'safe_dup' |
Need a full copy | require 'full_clone' | require 'full_dup' |
Notes
- Since none of these gems override the default clone and dup methods, the default behaviors remain available. Further, if multiple, differing requirements exists, more than one family member gem may be employed in the same project without fear of conflict.
- If multiple family gems are employed, they will each need to be installed and required into the application. See below for details.
- Meta-data attributes include the frozen status and singleton methods. However the tainted status is always copied.
Installation
Add this line to your application's Gemfile:
gem 'safe_clone'
And then execute:
$ bundle
Or install it yourself as:
$ gem install safe_clone
The safe_dup gem is at: ( https://rubygems.org/gems/safe_dup )
The safe_clone gem is at: ( https://rubygems.org/gems/safe_clone )
The full_dup gem is at: ( https://rubygems.org/gems/full_dup )
The full_clone gem is at: ( https://rubygems.org/gems/full_clone )
Usage
require 'safe_clone'
then, in those places where regular clone was problematic, use:
foo = my_object.safe_clone
instead of
begin
foo = my_object.clone
rescue TypeError
foo = my_object
end
It is actually pretty easy to determine where safe_clone needs to be used. It's those places where the clone method is generating unwanted exceptions.
Demo
A test bed for experimenting with the safe_clone gem is available as a rake task:
$ rake console
Performance
A reasonable question to raise is "How does safe clone compare with just catching the exception and handling it?" The benchmark sets a a realistic scenario where an array (whose contents may be varied) is having its contents cloned. The benchmarking code follows:
require "benchmark/ips"
require 'safe_clone'
class Array
def use_clone
self.map do |element|
begin
element.clone
rescue TypeError
element
end
end
end
def use_safe_clone
self.map {|element| element.safe_clone }
end
end
X = ["Test", :test, 43, true, nil, false]
Benchmark.ips do |x|
x.report("Clone with standard clone method") { X.use_clone }
x.report("Clone with the safe clone method") { X.use_safe_clone }
x.compare!
end
Results: ruby 1.9.3p484 (2013-11-22) [i386-mingw32]
C:\Sites\safe_clone>ruby bench\bench.rb
Warming up --------------------------------------
Clone with standard clone method
1.247k i/100ms
Clone with the safe clone method
35.027k i/100ms
Calculating -------------------------------------
Clone with standard clone method
12.957k (± 5.8%) i/s - 64.844k
Clone with the safe clone method
534.740k (± 8.9%) i/s - 2.662M
Comparison:
Clone with the safe clone method: 534740.1 i/s
Clone with standard clone method: 12956.6 i/s - 41.27x slower
Results: ruby 2.1.6p336 (2015-04-13 revision 50298) [i386-mingw32]
C:\Sites\safe_clone>ruby bench\bench.rb
Warming up --------------------------------------
Clone with standard clone method
4.945k i/100ms
Clone with the safe clone method
38.109k i/100ms
Calculating -------------------------------------
Clone with standard clone method
54.491k (± 7.3%) i/s - 271.975k
Clone with the safe clone method
569.236k (±10.2%) i/s - 2.820M
Comparison:
Clone with the safe clone method: 569236.4 i/s
Clone with standard clone method: 54491.3 i/s - 10.45x slower
Results: ruby 2.2.3p173 (2015-08-18 revision 51636) [i386-cygwin]
Peter Camilleri@NCC1701G /cygdrive/c/sites/safe_clone
$ ruby bench/bench.rb
Warming up --------------------------------------
Clone with standard clone method
3.698k i/100ms
Clone with the safe clone method
28.999k i/100ms
Calculating -------------------------------------
Clone with standard clone method
40.076k (± 5.1%) i/s - 203.390k
Clone with the safe clone method
481.524k (±10.0%) i/s - 2.407M
Comparison:
Clone with the safe clone method: 481524.1 i/s
Clone with standard clone method: 40075.6 i/s - 12.02x slower
Overall: Shorter code and faster. Winner, winner, chicken dinner!
Contributing
Plan A
- Fork it ( https://github.com/PeterCamilleri/safe_clone/fork )
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create a new Pull Request
Plan B
Go to the GitHub repository and raise an issue calling attention to some aspect that could use some TLC or a suggestion or an idea.
License
The gem is available as open source under the terms of the MIT License.
Code of Conduct
Everyone interacting in the fully_freeze project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.