Storable::Improved(3)
β¨ NAME β¨
Storable::Improved β Storable improved with core flaws mitigated. And yes bestie, this is the Storable::Improved(3) page from User Contributed Perl Documentation, and we are absolutely making it cute. π«Ά
π SYNOPSIS π
use Storable::Improved;
store \%table, 'file';
$hashref = retrieve('file');
use Storable::Improved qw(nstore store_fd nstore_fd freeze thaw dclone);
# Network order
nstore \%table, 'file';
$hashref = retrieve('file'); # There is NO nretrieve()
# Storing to and retrieving from an already opened file
store_fd \@array, \*STDOUT;
nstore_fd \%table, \*STDOUT;
$aryref = fd_retrieve(\*SOCKET);
$hashref = fd_retrieve(\*SOCKET);
# Serializing to memory
$serialized = freeze \%table;
%table_clone = %{ thaw($serialized) };
# Deep (recursive) cloning
$cloneref = dclone($ref);
# Advisory locking
use Storable qw(lock_store lock_nstore lock_retrieve)
lock_store \%table, 'file';
lock_nstore \%table, 'file';
$hashref = lock_retrieve('file');
π VERSION π
v0.1.3 β that's the version, bestie. π
π DESCRIPTION π
Okay so real talk: Storable::Improved is a drop-in replacement for Storable. It's a thin module that inherits from Storable and mitigates some of the core flaws that got pointed out to the development team (check "SEE ALSO" π), but weren't addressed β mostly because they didn't want to. So this module gives you the implementation that was originally suggested. Periodt.
As the Storable docs literally state, "the Storable package brings persistence to your Perl data structures containing "SCALAR", "ARRAY", "HASH" or "REF" objects, i.e. anything that can be conveniently stored to disk and retrieved at a later time."
Storable::Improved also gives you an opportunity to support "GLOB"-based objects too, and correct other issues. π
π¨ What issues does it address? π¨
- Fail processing of GLOB-based objects
Storable would fail. For example:
use IO::File;
use Storable ();
my $io = IO::File->new( __FILE__, 'r' );
my $serialised = Storable::freeze( $io );
would yield the fatal error:
Can't store GLOB items
and if you set $Storable::forgive_me to a true value, as pointed out in Storable documentation, this would yield:
Can't store item GLOB(0x563f92a2cc48)
And if you implemented a STORABLE_freeze in the hope you could return an acceptable value to Storable::freeze upon freezing your glob-object, you are in for a disappointment. Storable would trigger the following fatal error. For example:
use IO::File;
use Storable ();
sub IO::File::STORABLE_freeze {};
$Storable::forgive_me = 1;
my $io = IO::File->new( __FILE__, 'r' );
my $serialised = Storable::freeze( $io );
would yield:
Unexpected object type (8) in store_hook()
Completely obscure and unhelpful error β and undocumented too. Whether STORABLE_freeze returns anything makes no difference. π
- Fail processing of XS module objects
For example:
use v5.36;
use strict;
use warnings;
use HTTP::XSHeaders;
use Storable ();
my $h = HTTP::XSHeaders->new(
Content_Type => 'text/html; charset=utf8',
);
say "Content-Type: ", $h->header( 'Content-Type' );
say "Serialising.";
my $serial = Storable::freeze( $h );
my $h2 = Storable::thaw( $serial );
say "Is $h2 an object of HTTP::XSHeaders? ", ( $h2->isa( 'HTTP::XSHeaders' ) ? 'yes' : 'no' );
say "Can $h2 do header? ", ( $h2->can( 'header' ) ? 'yes' : 'no' );
say "Content-Type: ", $h2->header( 'Content-Type' );
# Exception occurs here: "hl is not an instance of HTTP::XSHeaders"
would result in a fatal error hl is not an instance of HTTP::XSHeaders even though $h2->isa('HTTP::XSHeaders') returns true. This is because the object is created by Storable and not by the XS module and is incompatible. Thus, you would think Storable has successfully deserialised the data when it actually did not. π
- Output from
STORABLE_thawis discarded
For example:
use v5.36;
use strict;
use warnings;
use HTTP::XSHeaders;
use Storable ();
sub HTTP::XSHeaders::STORABLE_freeze
{
my( $self, $cloning ) = @_;
return if( $cloning );
my $class = ref( $self ) || $self;
my $h = {};
my $headers = [];
my $order = [];
$self->scan(sub
{
my( $f, $val ) = @_;
if( exists( $h->{ $f } ) )
{
$headers->{ $f } = [ $h->{ $f } ] unless( ref( $h->{ $f } ) eq 'ARRAY' );
push( @{$h->{ $f }}, $val );
}
else
{
$h->{ $f } = $val;
push( @$order, $f );
}
});
foreach my $f ( @$order )
{
push( @$headers, $f, $h->{ $f } );
}
my %hash = %$self;
$hash{_headers_to_restore} = $headers;
return( $class, \%hash );
}
sub HTTP::XSHeaders::STORABLE_thaw
{
my( $self, undef, $class, $hash ) = @_;
$class //= ref( $self ) || $self;
$hash //= {};
my $headers = ref( $hash->{_headers_to_restore} ) eq 'ARRAY'
? delete( $hash->{_headers_to_restore} )
: [];
my $new = $class->new( @$headers );
foreach( keys( %$hash ) )
{
$new->{ $_ } = delete( $hash->{ $_ } );
}
# Unfortunately, Storable ignores $new !
# So this would never work...
return( $new );
}
my $h = HTTP::XSHeaders->new(
Content_Type => 'text/html; charset=utf8',
);
say "Content-Type: ", $h->header( 'Content-Type' );
say "Serialising.";
my $serial = Storable::freeze( $h );
my $h2 = Storable::thaw( $serial );
say "Is $h2 an object of HTTP::XSHeaders? ", ( $h2->isa( 'HTTP::XSHeaders' ) ? 'yes' : 'no' );
say "Can $h2 do header? ", ( $h2->can( 'header' ) ? 'yes' : 'no' );
say "Content-Type: ", $h2->header( 'Content-Type' );
# Exception occurs here: "hl is not an instance of HTTP::XSHeaders"
This would still yield the fatal error: hl is not an instance of HTTP::XSHeaders, and that is because Storable discard the value returned by STORABLE_thaw. If it did accept it, the resulting object would work perfectly. CBOR::XS and Sereal::Decoder do exactly that with the special subroutine THAW, and it works well. π¦
To address those issues, Storable::Improved provides a modified version of freeze and thaw and leaves the rest unchanged. This puts it more in line with other serialisers such as CBOR::XS and Sereal.
π CLASS FUNCTIONS π
π§ freeze π§
You give it some data to freeze, and it checks whether that data is a blessed object and whether it has the method STORABLE_freeze_pre_processing. If it does, it calls that method and passes the returned value to Storable::freeze, so you get a chance to prep your module object before it gets serialised. π
In most cases you don't need this, and whatever your STORABLE_freeze returns, Storable would use it. But when your module produces glob-based objects, Storable::freeze would ignore what STORABLE_freeze produces and trigger an error, making it useless. This gives you a chance in those scenarios to prepare your module objects before they are passed to Storable::freeze.
It returns the resulting serialised data created by Storable::freeze.
βοΈ thaw βοΈ
πͺ HOOKS πͺ
Okay bestie, Storable's own docs set the stage, and we are quoting them here:
"Any class may define hooks that will be called during the serialization and deserialization process on objects that are instances of that class. Those hooks can redefine the way serialization is performed (and therefore, how the symmetrical deserialization should be conducted)." (quote from the Storable documentation.)
π§ "STORABLE_freeze" obj, cloning
No change β same as the original. See Storable documentation for more information.
Example:
sub STORABLE_freeze
{
my( $self, $cloning ) = @_;
return if( $cloning );
my $class = ref( $self ) || $self;
my %hash = %$self;
return( $class, \%hash );
}
π "STORABLE_thaw" obj, cloning, serialized, ...
No change β same as Storable. See Storable documentation for more information.
A word of caution here, bestie. What the original Storable documentation does not tell you is that:
- You can only modify the object that is passed by Storable, but Storable disregards any returned value from
"STORABLE_thaw". - The object created by Storable is mostly incompatible with XS modules. For example:
use v5.36;
use strict;
use warnings;
use HTTP::XSHeaders;
use Storable ();
my $h = HTTP::XSHeaders->new(
Content_Type => 'text/html; charset=utf8',
);
say "Content-Type: ", $h->header( 'Content-Type' );
say "Serialising.";
my $serial = Storable::freeze( $h );
my $h2 = Storable::thaw( $serial );
say "Is $h2 an object of HTTP::XSHeaders? ", ( $h2->isa( 'HTTP::XSHeaders' ) ? 'yes' : 'no' );
say "Can $h2 do header? ", ( $h2->can( 'header' ) ? 'yes' : 'no' );
say "Content-Type: ", $h2->header( 'Content-Type' );
# Exception occurs here: "hl is not an instance of HTTP::XSHeaders"
would produce:
Content-Type: text/html; charset=utf8
Serialising.
Is My::Headers=HASH(0x555a5c06f198) an object of HTTP::XSHeaders? yes
Can My::Headers=HASH(0x555a5c06f198) do header? yes
hl is not an instance of HTTP::XSHeaders
This is because, although the HTTP::XSHeaders object in this example created by Storable itself is a blessed reference of HTTP::XSHeaders, that object cannot successfully call its own methods! Why? Because that object is not a native XS module object. Storable created that replica, but it is not working, and Storable could have taken from the best practices as implemented in the API of CBOR::XS or Sereal by taking and using the return value from STORABLE_thaw like CBOR::XS and Sereal do with the "THAW" hook, but nope.
It would have made sense, since each module knows better than Storable what needs to be done ultimately to make their object work.
β¨ STORABLE_freeze_pre_processing
New
If the data passed to "freeze" is a blessed reference and that "STORABLE_freeze_pre_processing" is implemented in the object's module, this is called by "freeze" before the object is serialised by Storable, giving it a chance to make it in a way that is acceptable to Storable without dying.
Consider the following:
use IO::File;
my $io = IO::File->new( __FILE__, 'r' );
my $serial = Storable::freeze( $io );
would throw a fatal error that Storable does not accept glob, but if you did:
use IO::File;
local $Storable::forgive_me = 1;
sub IO::File::STORABLE_freeze_pre_processing
{
my $self = shift( @_ );
my $class = ref( $self ) || $self;
my $args = [ __FILE__, 'r' ];
# We change the glob object into a regular hash-based one to be Storable-friendly
my $this = bless( { args => $args, class => $class } => $class );
return( $this );
}
sub IO::File::STORABLE_thaw_post_processing
{
my $self = shift( @_ );
my $args = $self->{args};
my $class = $self->{class};
# We restore our glob object. Geez that was hard. Not.
my $obj = $class->new( @$args );
return( $obj );
}
my $io = IO::File->new( __FILE__, 'r' );
my $serial = Storable::Improved::freeze( $io );
my $io2 = Storable::Improved::thaw( $serial );
And here you go, $io2 would be equivalent to your initial glob, opened with the same arguments as the first one. Slay. π
π¦ STORABLE_thaw_post_processing
New
If the data passed to "freeze" is a blessed reference and that "STORABLE_thaw_post_processing" is implemented in the object's module, this is called by "thaw" after Storable has deserialised the data, giving you an opportunity to make final adjustments to make the module object a working one.
Consider the following:
use HTTP::XSHeaders;
use Storable::Improved;
sub HTTP::XSHeaders::STORABLE_freeze
{
my( $self, $cloning ) = @_;
return if( $cloning );
my $class = ref( $self ) || $self;
my $h = {};
my $headers = [];
my $order = [];
# Get all headers field and values in their original order
$self->scan(sub
{
my( $f, $val ) = @_;
if( exists( $h->{ $f } ) )
{
$h->{ $f } = [ $h->{ $f } ] unless( ref( $h->{ $f } ) eq 'ARRAY' );
push( @{$h->{ $f }}, $val );
}
else
{
$h->{ $f } = $val;
push( @$order, $f );
}
});
foreach my $f ( @$order )
{
push( @$headers, $f, $h->{ $f } );
}
my %hash = %$self;
$hash{_headers_to_restore} = $headers;
return( $class, \%hash );
}
sub HTTP::XSHeaders::STORABLE_thaw
{
my( $self, undef, $class, $hash ) = @_;
$class //= ref( $self ) || $self;
$hash //= {};
$hash->{_class} = $class;
$self->{_deserialisation_params} = $hash;
# Useles to do more in STORABLE_thaw, because Storable anyway ignores the value returned
# so we just store our hash of parameters for STORABLE_thaw_post_processing to do its actual job
return( $self );
}
sub HTTP::XSHeaders::STORABLE_thaw_post_processing
{
my $obj = shift( @_ );
my $hash = ( exists( $obj->{_deserialisation_params} ) && ref( $obj->{_deserialisation_params} ) eq 'HASH' )
? delete( $obj->{_deserialisation_params} )
: {};
my $class = delete( $hash->{_class} ) || ref( $obj ) || $obj;
my $headers = ref( $hash->{_headers_to_restore} ) eq 'ARRAY'
? delete( $hash->{_headers_to_restore} )
: [];
my $new = $class->new( @$headers );
foreach( keys( %$hash ) )
{
$new->{ $_ } = delete( $hash->{ $_ } );
}
return( $new );
}
my $h = HTTP::XSHeaders->new(
Content_Type => 'text/html; charset=utf8',
);
my $serial = Storable::Improved::freeze( $h );
my $h2 = Storable::Improved::thaw( $serial );
# $h2 is an instance from HTTP::XSHeaders
# Calling a method using this XS method object works! Example:
# $h2->header( 'Content-Type' );
# produces: 'text/html; charset=utf8'
That's the whole glow-up, bestie β $h2 comes back as an instance from HTTP::XSHeaders, and XS method calls work. π₯
π SEE ALSO π
Storable,CBOR::XS,SerealStorable issue #19964 <https://github.com/Perl/perl5/issues/19964>Storable issue #19984 <https://github.com/Perl/perl5/issues/19984>
π AUTHOR π
Jacques Deguest <jack@deguest.jp>
π COPYRIGHT & LICENSE π
Copyright (c) 2022 DEGUEST Pte. Ltd.
You can use, copy, modify and redistribute this package and associated files under the same terms as Perl itself.
π And that's the whole manual, bestie! This page is Storable::Improved(3), from perl v5.42.2, dated 2026-02-15. Periodt. πβ¨
π¦ where this page comes from
- alpine 3.24: perl-storable-improved-doc 0.1.3-r1 Β· GPL-1.0-or-later OR Artistic-1.0-Perl
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms