The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

NAME

Result::Trait - the trait which all Result objects implement head1 SYNOPSIS

use results;

sub to_uppercase {
  my $str = shift;
  
  return err( "Cannot uppercase a reference." ) if ref $str;
  return err( "Cannot uppercase undef." ) if not defined $str;
  
  return ok( uc $str );
}

my $got = to_uppercase( "hello world" )->unwrap();

DESCRIPTION

The err, ok, and ok_list functions from results return objects which have the methods described in this trait.

Methods

These methods are available on all Result objects.

Many of them will mark the Result as "handled". All Results should be handled.

$result->and( $other_result )

Returns $result if it is an err. Returns $other_result otherwise. The effect of this is that and returns an ok Result only if both Results are ok, and an err Result otherwise.

$result is considered to be handled if it was ok. $other_result is not considered to have been handled.

Example

Supposing the create_file() and upload_file() functions return Results to indicate success:

my $result = create_file()->and( upload_file() );

if ( $result->is_err() ) {
  warn $result->unwrap_err;
}
else {
  say "File created and uploaded successfully!";
  $result->unwrap();
}

Note that if the create_file() function failed, the upload_file() will still be run even though it is destined to fail, because method arguments are evaluated eagerly in Perl. For a solution, see and_then().

$result->and_then( sub { THEN } )

The coderef is expected to return a Result object.

Returns $result if it is an err.

If $result is ok, then executes the coderef and returns the coderef's Result. Within the coderef, the unwrapped value of $result in scalar context is available as $_ and in list context is available as @_.

$result is considered to be handled if it was ok. $other_result is not considered to have been handled.

Effectively a version of and with lazy evaluation of the second operand.

Example

Supposing the create_file() and upload_file() functions return Results to indicate success:

my $result = create_file()->and_then( sub { upload_file() } );

if ( $result->is_err() ) {
  warn $result->unwrap_err;
}
else {
  say "File created and uploaded successfully!";
  $result->unwrap();
}

$result->err()

For err Results, the same as unwrap. For ok Results, returns nothing.

The Result is considered to be handled.

$result->expect( $msg )

For ok Results, unwraps the result.

For err Results, throws an exception with the given message.

The Result is considered to be handled.

$result->expect_err( $msg )

For ok Results, throws an exception with the given message.

For err Results, unwraps the result.

This is the inverse of expect().

The Result is considered to be handled.

$result->flatten()

If this is an ok Result containing another Result, returns the inner Result. The outer Result is considered to be handled.

If this is an ok Result not containing another Result, throws.

If this is an err Result, returns self. The Result is not considered handled.

Note this is not a recursive flatten. It only flattens one level of Results.

$result->inspect( sub { PEEK } )

If this is an ok Result, runs the coderef. Within the coderef, the unwrapped value in scalar context is available as $_ and in list context is available as @_.

If this is an err Result, does nothing.

Always returns self, making it suitable for chaining.

The Result is not considered handled.

$result->inspect_err( sub { PEEK } )

If this is an ok Result, does nothing.

If this is an err Result, runs the coderef. Within the coderef, the unwrapped error in scalar context is available as $_ and in list context is available as @_.

Always returns self, making it suitable for chaining.

This is the inverse of inspect().

The Result is not considered handled.

$result->is_err()

Returns true if and only if this is an err Result.

The Result is not considered handled.

$result->is_ok()

Returns true if and only if this is an ok Result.

The Result is not considered handled.

$result->map( sub { MAP } )

If the Result is ok, then runs the coderef. Within the coderef, the unwrapped value in scalar context is available as $_ and in list context is available as @_. The return value of the coderef is wrapped in a new ok Result. The original Result is considered to be handled.

If the Result is err, then returns self. The Result is not considered handled.

Example

In the example below, uppercase_name() will return a Result which may be an ok Result with the uppercased name from the database or the err Result with the message "Could not connect to database".

sub get_name {
  if ( connect_to_database() ) {
    ...;
    return ok( $name );
  }
  else {
    return err( "Could not connect to database" );
  }
}

sub uppercase_name {
  return get_name()->map( sub {
    return uc $_;
  } );
}

sub lowercase_name {
  return get_name()->map( sub {
    return lc $_;
  } );
}

$result->map_err( sub { MAP } )

If the Result is ok, then returns self. The Result is not considered handled.

If the Result is err, then runs the coderef. Within the coderef, the unwrapped error in scalar context is available as $_ and in list context is available as @_. The return value of the coderef is wrapped in a new err Result. The original Result is considered to be handled.

This is the inverse of map().

$result->map_or( @default, sub { MAP } )

If the Result is ok, then runs the coderef. Within the coderef, the unwrapped value in scalar context is available as $_ and in list context is available as @_. Returns the return value of the coderef.

If the Result is err, then returns @default (whih may just be a single scalar).

Note that unlike map(), this does not return a Result, but a value.

The Result is considered to be handled.

Example

In the example below, uppercase_name() will return a Result which may be an ok Result with the uppercased name from the database or the err Result with the message "Could not connect to database".

sub get_name {
  if ( connect_to_database() ) {
    ...;
    return ok( $name );
  }
  else {
    return err( "Could not connect to database" );
  }
}

say "HELLO, ", get_name()->map_or( "ANON", sub {
  return uc $_;
} );

$result->map_or_else( sub { DEFAULT }, sub { MAP } )

Similar to map_or() except that the @default is replaced by a coderef which should return the default.

The Result is considered to be handled.

$result->match( %dispatch_table )

The %dispatch_table is a hash of coderefs.

The keys 'ok' and 'err' are required coderefs to handle ok and err Results.

(Additional coderefs with keys "err_XXX" are allowed, where "XXX" is a short name for a kind of error. If match is called on an err Result, and the error is a blessed object which DOES the "results::exceptions" trait, then $result->unwrap_err()->err_kind() is called and expected to return a string indicating the error kind. The results::exceptions module makes it very easy to create exception objects like this!)

The unwrapped value or error is available in $_ and @_ as you might expect.

This method is not found in the original Rust implementation of Results.

Example

get_name()->match(
  ok   => sub { say "Hello, $_" },
  err  => sub { warn $_ },
);

Example

open_file($filename)->match(
  ok            => sub { $_->write( $data ) },
  err_Auth      => sub { die( "Permissions error!" ) },
  err_DiskFull  => sub { die( "Disk is full!" ) },
  err           => sub { die( "Another error occurred!" ) },
);

$result->ok()

For ok Results, the same as unwrap. For err Results, returns nothing.

The Result is considered to be handled.

$result->or( $other_result )

Returns $result if it is ok. Returns $other_result otherwise. The effect of this is that or returns an ok Result if either of the Results is ok, and an err Result if both results were err Results.

$result is considered to be handled if it was an err. $other_result is not considered to have been handled.

Example

If retrieve_file() uses a Result to indicate success:

retrieve_file( "server1.example.com" )
  ->or( retrieve_file( "server2.example.com" ) )
  ->or( retrieve_file( "server3.example.com" ) )
  ->expect( "Could not retrieve file from any server!" );

Like with and(), it needs to be noted that Perl eagerly evaluates method call arguments, so retrieve_file() will be called three times, even if the first server succeeded. or_else() provides a solution.

$result->or_else( sub { ELSE } )

The coderef is expected to return a Result object.

Returns $result if it is ok.

Otherwise, executes the coderef and returns the coderef's Result. Within the coderef, the unwrapped error in scalar context is available as $_ and in list context is available as @_.

$result is considered to be handled if it was an err.

Example

If retrieve_file() uses a Result to indicate success:

retrieve_file( "server1.example.com" )
  ->or_else( sub {
    return retrieve_file( "server2.example.com" );
  } )
  ->or_else( sub {
    return retrieve_file( "server3.example.com" );
  } )
  ->expect( "Could not retrieve file from any server!" );

$result->type( $constraint )

If this Result is an err, returns self. Not considered handled.

If this Result is ok, and passes the type constraint in scalar context, returns self. Not considered handled.

Otherwise returns an err Result with the type validation error message. In this case the original Result is considered handled.

This method is not found in the original Rust implementation of Results.

Example

If get_config() returns an ok Result containing a hashref, then:

use Types::Common qw( HashRef );

my $config = get_config->type( HashRef )->unwrap();

$result->type_or( @default, $constraint )

If this Result is an err, returns self. Not considered handled.

If this Result is ok, and passes the type constraint in scalar context, returns self. Not considered handled.

Otherwise returns an ok Result with the default value(s). In this case the original Result is considered handled.

This method is not found in the original Rust implementation of Results.

Example

If get_config() returns an ok Result containing a hashref, then:

use Types::Common qw( HashRef );

my $config = get_config->type_or( {}, HashRef )->unwrap();

$result->type_or_else( sub { ELSE }, $constraint )

If this Result is an err, returns self. Not considered handled.

If this Result is ok, and passes the type constraint in scalar context, returns self. Not considered handled.

Otherwise executes the coderef, which is expected to return a Result. In this case the original Result is considered handled.

This method is not found in the original Rust implementation of Results.

$result->unwrap()

For ok Results, returns the value and the Result is considered handled.

For err Results, throws an exception. If you wish to customize the error message, use expect() instead of unwrap().

$result->unwrap_err()

For err Results, returns the error and the Result is considered handled.

For ok Results, throws an exception. If you wish to customize the error message, use expect_err() instead of unwrap_err().

$result->unwrap_or( @default )

For ok Results, returns the value and the Result is considered handled.

For err Results, returns the default value(s).

$result->unwrap_or_else( sub { ELSE } )

For ok Results, returns the value and the Result is considered handled.

For err Results, executes the coderef and returns whatever the coderef returned.

This is effectively a lazy version of unwrap_or().

$result->DESTROY()

You should not call this method directly. Called by Perl when the object goes out of scope or is otherwise destroyed.

Attempts to throw an exception if the Result has not been handled. However, the current implementation of Perl downgrades exceptions thrown by DESTROY to be warnings.

Implementing This Trait

This module uses Role::Tiny.

Implementations of this trait need to provide the following methods:

is_err(), is_ok()

As documented above.

_handled()

A getter/setter for whether the Result has been handled.

_peek()

If the Result is ok, should return the inner value. Should pay attention to list versus scalar context.

Undefined behaviour for err Results.

_peek_err()

If the Result is err, should return the inner value. Should pay attention to list versus scalar context.

Undefined behaviour for ok Results.

Implementations may override methods to provide more efficient versions. In particular, unwrap and unwrap_err are used a lot internally, but the default implementations are not the fastest.

BUGS

Please report any bugs to https://github.com/tobyink/p5-results/issues.

SEE ALSO

results, https://doc.rust-lang.org/std/result/enum.Result.html.

The unit tests for Result::Trait should provide useful clarifying examples: https://github.com/tobyink/p5-results/blob/master/t/unit/Result/Trait.t.

AUTHOR

Toby Inkster <tobyink@cpan.org>.

COPYRIGHT AND LICENCE

This software is copyright (c) 2022 by Toby Inkster.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.

DISCLAIMER OF WARRANTIES

THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.