Swift type modelling the success/failure of arbitrary operations.

Overview

Result

Build Status Carthage compatible CocoaPods Reference Status

This is a Swift µframework providing Result<Value, Error>.

Result<Value, Error> values are either successful (wrapping Value) or failed (wrapping Error). This is similar to Swift’s native Optional type: success is like some, and failure is like none except with an associated Error value. The addition of an associated Error allows errors to be passed along for logging or displaying to the user.

Using this µframework instead of rolling your own Result type allows you to easily interface with other frameworks that also use Result.

Use

Use Result whenever an operation has the possibility of failure. Consider the following example of a function that tries to extract a String for a given key from a JSON Dictionary.

typealias JSONObject = [String: Any]

enum JSONError: Error {
    case noSuchKey(String)
    case typeMismatch
}

func stringForKey(json: JSONObject, key: String) -> Result<String, JSONError> {
    guard let value = json[key] else {
        return .failure(.noSuchKey(key))
    }
    
    guard let value = value as? String else {
        return .failure(.typeMismatch)
    }

    return .success(value)
}

This function provides a more robust wrapper around the default subscripting provided by Dictionary. Rather than return Any?, it returns a Result that either contains the String value for the given key, or an ErrorType detailing what went wrong.

One simple way to handle a Result is to deconstruct it using a switch statement.

switch stringForKey(json, key: "email") {

case let .success(email):
    print("The email is \(email)")
    
case let .failure(.noSuchKey(key)):
    print("\(key) is not a valid key")
    
case .failure(.typeMismatch):
    print("Didn't have the right type")
}

Using a switch statement allows powerful pattern matching, and ensures all possible results are covered. Swift 2.0 offers new ways to deconstruct enums like the if-case statement, but be wary as such methods do not ensure errors are handled.

Other methods available for processing Result are detailed in the API documentation.

Result vs. Throws

Swift 2.0 introduces error handling via throwing and catching Error. Result accomplishes the same goal by encapsulating the result instead of hijacking control flow. The Result abstraction enables powerful functionality such as map and flatMap, making Result more composable than throw.

Since dealing with APIs that throw is common, you can convert such functions into a Result by using the materialize method. Conversely, a Result can be used to throw an error by calling dematerialize.

Higher Order Functions

map and flatMap operate the same as Optional.map and Optional.flatMap except they apply to Result.

map transforms a Result into a Result of a new type. It does this by taking a function that transforms the Value type into a new value. This transformation is only applied in the case of a success. In the case of a failure, the associated error is re-wrapped in the new Result.

// transforms a Result<Int, JSONError> to a Result<String, JSONError>
let idResult = intForKey(json, key:"id").map { id in String(id) }

Here, the final result is either the id as a String, or carries over the failure from the previous result.

flatMap is similar to map in that it transforms the Result into another Result. However, the function passed into flatMap must return a Result.

An in depth discussion of map and flatMap is beyond the scope of this documentation. If you would like a deeper understanding, read about functors and monads. This article is a good place to start.

Integration

Carthage

  1. Add this repository as a submodule and/or add it to your Cartfile if you’re using carthage to manage your dependencies.
  2. Drag Result.xcodeproj into your project or workspace.
  3. Link your target against Result.framework.
  4. Application targets should ensure that the framework gets copied into their application bundle. (Framework targets should instead require the application linking them to include Result.)

Cocoapods

pod 'Result', '~> 5.0'

Swift Package Manager

// swift-tools-version:4.0
import PackageDescription

let package = Package(
    name: "MyProject",
    targets: [],
    dependencies: [
        .package(url: "https://github.com/antitypical/Result.git",
                 from: "5.0.0")
    ]
)
Comments
  • RFC: Drop the ErrorType constraint

    RFC: Drop the ErrorType constraint

    I'm not sure what constraining the second generic placeholder to ErrorTypegives us. IIRC this has come up during discussions on other issues as well.

    If there is stuff that only works on ErrorTypes we can always add that to a protocol extension like so:

    extension ResultType where Error: ErrorType { }
    
    opened by Thomvis 19
  • [RFC] Use Runes as an optional dependency for functional operators

    [RFC] Use Runes as an optional dependency for functional operators

    Been thinking about this a lot lately, and wanted to open up a discussion about it.

    Issues like #49 seem like a smell to me. Needing to maintain duplicate and parallel operator definitions in order to maintain compatibility with other libraries screams "shared dependency" to me. Ideally, I think Runes (or a similar lib) would define the operators, and then Result would define implementations for the types it exposes. That would allow the operators to have a single point of definition, and guarantee consistency for things like precedence.

    Obviously, it's not that simple. Adding Runes as a dependency would mean needing to expose another framework that users would need to link against, even if they never use it. I think we're all in agreement that a lib like this should have as few dependencies as possible. Box makes sense, but adding in a dependency on Runes (especially after we've already pulled out the dependencies on Either and Prelude) doesn't.

    Ideally, we'd be able to do some sort of optional dependency. It would be awesome to be able to say "Is Runes available? If so, here's this implementation of flatMap for result that you can use". (It would be more awesome to be able to define some sort of generic type for this. Like a higher kind of type. idk) But unfortunately Apple hasn't provided a way to do that.

    As a temporary solution, what if the duplicate operators were moved into a second framework that included Runes as a dependency, leaving the basic Result framework pure?

    PROS

    • Users that never use >>- or any of the other operators would never see them, and would never need to worry about them
    • Users that do use >>- and so forth wouldn't have to worry about conflicting operator definitions.
    • Using a common dependency that implements these same operators for Optional and Array would theoretically increase exposure to these operators, driving education and adoption.

    CONS

    • This would require users that want >>- to import a module that is named something other than Result. ResultRunes or ResultOperators or whatever. That's fairly gross.
    • When using Carthage to install Result, users would be left with 5(!) frameworks in their build directory. They would then have to know which ones to link to get what they want.

    As a side note, this would be able to be implemented for CocoaPods by using subspecs. CP users would then only need to import Result no matter what, using a spec similar to this:

    Pod::Spec.new do |s|
      s.name         = 'Result'
      s.version      = '0.4.3'
      s.summary      = 'Swift type modelling the success/failure of arbitrary operations'
    
      s.homepage     = 'https://github.com/antitypical/Result'
      s.license      = 'MIT'
      s.author       = { 'Rob Rix' => '[email protected]' }
      s.source       = { :git => 'https://github.com/antitypical/Result.git', :tag => s.version.to_s }
      s.source_files  = 'Result/Result.swift'
      s.requires_arc = true
      s.ios.deployment_target = '8.0'
      s.osx.deployment_target = '10.9'
    
      subspec 'Runes' do |subspec|
        subspec.source_files = 'Result/Runes.swift'
        subspec.dependency 'Runes', '>= 1.2.2'
      end
    end
    

    CP users would then be able to declare pod 'Result/Runes' and they would get both.

    I don't think this PR should be used as the final implementation by the way, but meant it more as an example of how this could work in the service of conversation.

    opened by gfontenot 19
  • more in-depth README

    more in-depth README

    This is my attempt to help out with #51.

    I'm not sure how great my example is... but I think it does demonstrate the core concepts well. And it was the first example I thought of.

    I also tried to give a shoutout to map and flatMap, and a reference to Javi Soto's monads article (without mentioning the words Functor or Monad in the README). I'm not sure if this is a horrible idea. Seems like now that Swift 2.0 brings error handling the higher order functions are really what make results stand out as a better (IMO) solution to error handling. Since those features are one of the compelling reasons for Result I thought it might be appropriate to highlight them a little.

    Anyway let me know what you think. Hopefully it's helpful.

    opened by jarsen 14
  • Reconsider ErrorTypeConvertible

    Reconsider ErrorTypeConvertible

    This was changed in https://github.com/antitypical/Result/pull/141. But the current form does not looks correct since the ConvertibleType associated type is not used in any method signature.

    This change ~~brings back the original definition~~ removes ConvertibleType from ErrorTypeConvertible and worked well for both Swift 2.2 on OS X and swift-DEVELOPMENT-SNAPSHOT-2016-03-24-a on Linux.

    opened by ikesyo 13
  • Swift 4.1

    Swift 4.1

    opened by NachoSoto 12
  • Add function version of `materialize`

    Add function version of `materialize`

    CAUTION: This patch is also not working for now in the same way as #56 . https://twitter.com/gfontenot/status/608774708159324161 #56 is great, but function version of materialize is easier to derive Result from failable closure.

    let data = NSData()
    let result = materialize {
        try NSJSONSerialization.JSONObjectWithData(data, options: [])
    }
    
    opened by ishkawa 12
  • Avoid using custom operators in Swift 3

    Avoid using custom operators in Swift 3

    It seems >>- operator no longer works seamlessly with other operator-centric libraries e.g. Runes, Operadics, due to the new precedencegroup in Swift 3.

    Formerly in Swift 2, "error: ambiguous operator declarations found for operator" did not occur iff associativity and precedence were totally equal, but this workaround no longer works in Swift 3, so I would like to suggest to either:

    1. Remove custom operators in Result, or
    2. Add de facto infix-operator library as a dependency (if there's any)
    opened by inamiy 11
  • Add <^> (fmap) & <*> (ap) for applicative style support.

    Add <^> (fmap) & <*> (ap) for applicative style support.

    It will be nice if we can add Haskell's fmap and ap to support applicative style as https://github.com/thoughtbot/Argo 's Decoded<T> type can do.

    opened by inamiy 11
  • Using Result with Alamofire

    Using Result with Alamofire

    Hi,

    I just wanted to include Result in my project and am running across a few issues. It seems to me as if Alamofire (which is already a dependency) defines its own Result type throwing problems when trying to write functions that return results.

    For example Xcode (7.0 beta 5) tells me that I can't write Result<MyDataType, MyErrorType> because Generic type 'Result' specialized with too many type parameters (got 2, but expected 1). I'm basing my usage of Result on this.

    Both are linked as frameworks installed via Carthage in a Swift 2.0 project.

    I'm guessing issues like this shouldn't actually be occurring, but I'm doing something wrong here. Any pointers would be great, thank you!

    opened by kiliankoe 11
  • Type alias result without committing to an error type

    Type alias result without committing to an error type

    I would like to do something in the lines of the following: typealias OperationResult = Result and just have any ErrorType as the error type. But as it is now I have to declare a concrete ErrorType Result<valuetype, NSError> Which means I can't use the feature of swift where every enum can just be an ErrorType. I have to use NSError only. Any workarounds?

    opened by yoavschwartz 10
  • Move `import Foundation` statement to top in Result.swift

    Move `import Foundation` statement to top in Result.swift

    The standard Swift coding convention is to have import statements on the top of the file.

    Also, code completion results only show up after the import statement, so this change ensures that declarations from the Foundation module are correctly suggested in this file.

    opened by ahoppen 0
  • Result initializer works in for loop but not map?

    Result initializer works in for loop but not map?

    This code compiles without issue, where generatedEvents is of type [EKEvent] and eventStore is an EKEventStore:

    for thisEvent in generatedEvents {
        let result = Result(try eventStore.save(thisEvent, span: .thisEvent, commit: false))
        print(result)
    }
    

    However, when I rewrite it as a map (to generate an array of Results):

    let results = generatedEvents.map { thisEvent -> Result<(), AnyError> in
        let result = Result(try eventStore.save(thisEvent, span: .thisEvent, commit: false))
        print(result)
        return result
    }
    

    I receive the compiler error:

    Call can throw, but it is not marked with 'try' and the error is not handled

    However, looking at the Result initializer, it looks like the error is handled in a do/catch block.

    opened by getaaron 1
  • Adding a `fold` method

    Adding a `fold` method

    I have a few situations where I need to convert a Result to a common type, and looking at other languages fold seems to exist.

    Essentially, this:

    public extension Result {
    
        /// Convert either a success or failure into type `U`
        public func fold<U>(success: (Value) throws -> U, failure: (Error) throws -> U) rethrows -> U {
            switch self {
            case .success(let value): return try success(value)
            case .failure(let error): return try failure(error)
            }
        }
    }
    

    Good idea / bad idea?

    opened by deanWombourne 5
  • Non-throwing dematerialize for result using NoError

    Non-throwing dematerialize for result using NoError

    I'm pretty new to using Result, so perhaps this is possible by another concise means.

    A variant of dematerialize that doesn't throw if the error type is NoError would be nice to have. I typically avoid try! throughout my code, and use SwiftLint to warn against its use. My options here are to use a magic comment to disable the SwiftLint rule, or do/catch - neither is particularly appealing. Thoughts?

    opened by ileitch 1
  • Throw other errors within `init(attempt:)` and `tryMap`

    Throw other errors within `init(attempt:)` and `tryMap`

    When using Result in my project, I needed to use init(attempt:) in several places but I wanted to use my own wrapper for arbitrary Errors. The existing initializer only works with AnyError, so I've modified the code to allow using any Error which conforms to the ErrorInitializing protocol.

    opened by philtre 1
Releases(5.0.0)
  • 5.0.0(May 27, 2019)

  • 4.1.0(Jan 1, 2019)

    NEW

    • Add compatibility with Swift 5's Result (#278)
      • Add Result.Success typealias
      • Add Result.Failure typealias
      • Add init(catching body: () throws -> Success) and deprecate init(attempt f: () throws -> Value)
      • Add func get() throws -> Success and deprecate func dematerialize() throws -> Value

    See https://github.com/apple/swift-evolution/blob/master/proposals/0235-add-result.md and https://forums.swift.org/t/accepted-with-modifications-se-0235-add-result-to-the-standard-library/18603 for further information of the Swift 5's Result.

    Source code(tar.gz)
    Source code(zip)
  • 4.0.1(Dec 31, 2018)

  • 4.0.0(Apr 27, 2018)

    CHANGED

    • Result now requires Swift 4.0/Xcode 9 at least. Swift 4.1/Xcode 9.3 are supported as well (#217, #237, #241, #245, #252, #256, #258, #259, #264)
    • Remove deprecated and unavailable APIs from Result 2.x (Swift 2.x) (#220, #226)
    • Rename ErrorProtocolConvertible to ErrorConvertible (#232)
    • Reduce the responsibility of ResultProtocol (#235, #243)
    • Deprecate materialize functions (#239)
    • Make try functions unavailable (#253)

    NEW

    • Add LocalizedError conformance to AnyError on Linux (#227)
    • Add AnyError overloads for init(_:) and init(attempt:) (#236)
    Source code(tar.gz)
    Source code(zip)
  • 3.2.4(Sep 27, 2017)

  • 3.2.3(May 29, 2017)

  • 3.2.2(May 16, 2017)

  • 3.2.1(Mar 4, 2017)

  • 3.2.0(Mar 1, 2017)

    • [NEW]: Add ResultProtocol.bimap(success:failure:) method (#116, #205)
    • [NEW]: Add ResultProtocol.fanout(_:) as an alternative to &&& operator (#204)
    • [NEW]: Add LocalizedError conformance to AnyError on Darwin platforms (#210)
    • [CHANGED]: Deprecate >>- and &&& operators (#206)
    Source code(tar.gz)
    Source code(zip)
  • 3.1.0(Nov 29, 2016)

    • [NEW]: Add Equatable conformance to NoError (#200, #201)
    • [NEW]: Add type-erased AnyError for generic contexts. materialize overload for AnyError is also added (#198)
    • [CHANGED]: materialize for NSError is now deprecated in favor of the overload for AnyError (#198)
    Source code(tar.gz)
    Source code(zip)
    Result.framework.zip(2.25 MB)
  • 3.0.0(Sep 11, 2016)

    • [CHANGED]: Drop Swift 2.x support
    • [CHANGED]: Swift 3 API Design Guidelines
    • [CHANGED]: Support for Swift 3.0

    API changes from 2.x:

    • Result
      • .Success to .success
      • .Failure to .failure
    • ResultType to ResultProtocol
      • recoverWith(_:) to recover(with:)
    • ResultErrorType to Swift.Error
    • ErrorTypeConvertible to ErrorProtocolConvertible
      • errorFromErrorType_:) to error(from:)
    Source code(tar.gz)
    Source code(zip)
    Result.framework.zip(2.06 MB)
  • 3.0.0-alpha.4(Aug 24, 2016)

    • [CHANGED]: Drop Swift 2.x support
    • [CHANGED]: Swift 3 API Design Guidelines
    • [CHANGED]: Support for Swift 3.0 Preview 6

    API changes from 2.x:

    • Result
      • .Success to .success
      • .Failure to .failure
    • ResultType to ResultProtocol
      • recoverWith(_:) to recover(with:)
    • ResultErrorType to Swift.Error
    • ErrorTypeConvertible to ErrorProtocolConvertible
      • errorFromErrorType_:) to error(from:)
    Source code(tar.gz)
    Source code(zip)
  • 3.0.0-alpha.3(Aug 17, 2016)

    • [CHANGED]: Drop Swift 2.x support
    • [CHANGED]: Swift 3 API Design Guidelines
    • [CHANGED]: Support for Swift 3.0 Preview 4

    API changes from 2.x:

    • Result
      • .Success to .success
      • .Failure to .failure
    • ResultType to ResultProtocol
      • recoverWith(_:) to recover(with:)
    • ResultErrorType to Swift.Error
    • ErrorTypeConvertible to ErrorProtocolConvertible
      • errorFromErrorType_:) to error(from:)
    Source code(tar.gz)
    Source code(zip)
  • 3.0.0-alpha.2(Aug 7, 2016)

    • [CHANGED]: Drop Swift 2.x support
    • [CHANGED]: Swift 3 API Design Guidelines
    • [CHANGED]: Support for Swift 3.0 Preview 4

    API changes from 2.x:

    • Result
      • .Success to .success
      • .Failure to .failure
    • ResultType to ResultProtocol
      • recoverWith(_:) to recover(with:)
    • ResultErrorType to Swift.Error
    • ErrorTypeConvertible to ErrorProtocolConvertible
      • errorFromErrorType_:) to error(from:)
    Source code(tar.gz)
    Source code(zip)
  • 2.1.3(Jul 15, 2016)

  • 3.0.0-alpha.1(Jul 14, 2016)

    • [CHANGED]: Drop Swift 2.x support
    • [CHANGED]: Swift 3 API Design Guidelines
    • [CHANGED]: Support for Swift 3.0 Preview 1 and 2

    API changes from 2.x:

    • Result
      • .Success to .success
      • .Failure to .failure
    • ResultType to ResultProtocol
      • recoverWith(_:) to recover(with:)
    • ResultErrorType to ErrorProtocol
    • ErrorTypeConvertible to ErrorProtocolConvertible
      • errorFromErrorType_:) to error(from:)
    Source code(tar.gz)
    Source code(zip)
  • 2.1.2(Jul 1, 2016)

  • 2.1.1(Jun 18, 2016)

  • 2.1.0(Jun 10, 2016)

    • [CHANGED]: recover, recoverWith, ==, !=, ??, and >>- are now defined in terms of ResultType
    • [CHANGED]: ConvertibleType was removed from ErrorTypeConvertible
    • [CHANGED]: Swift 3 support has been updated to the 2016-05-31 snapshot
    • [NEW]: Unused results will now trigger a warning
    Source code(tar.gz)
    Source code(zip)
    Result.framework.zip(1.17 MB)
  • 2.0.0(Mar 27, 2016)

  • 1.0.2(Jan 29, 2016)

  • 1.0.1(Dec 9, 2015)

  • 1.0.0(Nov 26, 2015)

    :tada: 1.0! :tada:

    This has been in beta for a while. Thanks to everyone who helped improve it and push it forward during that time.

    Summary of changes since the 0.4 series of releases (See the 0.5 and 0.6 series of beta releases for more detailed info):

    • [NEW]: Swift 2.0 support
    • [NEW]: Translate to and from Swift 2 functions using throw
    • [NEW]: tvOS support
    • [CHANGED]: Removed dependency on Box
    Source code(tar.gz)
    Source code(zip)
  • 0.6.0-beta.6(Oct 31, 2015)

    • [fixed] Added Apple TV deployment target to the podspec
    • [fixed] Added second materialize without @autoclosure to be compatible with trailing closure syntax
    Source code(tar.gz)
    Source code(zip)
  • 0.6.0-beta.5(Oct 23, 2015)

  • 0.4.5(Oct 13, 2015)

  • 0.6.0-beta.4(Oct 8, 2015)

  • 0.6.0-beta.3(Sep 16, 2015)

  • 0.6.0-beta.2(Sep 15, 2015)

    • [new] free materialize function that takes a function of the type () throws -> T and returns Result<T, NSError>
    • [new] tvOS Support
    • [Fixed] Explicitly set code signing to work around bugs in xcodebuild
    Source code(tar.gz)
    Source code(zip)
  • 0.5(Jun 9, 2015)

Owner
Antitypical
Antitypical
Commonly used data structures for Swift

Swift Collections is an open-source package of data structure implementations for the Swift programming language.

Apple 2.7k Jan 5, 2023
Fast sorted collections for Swift using in-memory B-trees

Fast Sorted Collections for Swift Using In-Memory B-Trees Overview Reference Documentation Optimizing Collections: The Book What Are B-Trees? Why In-M

null 1.3k Dec 20, 2022
Examples of commonly used data structures and algorithms in Swift.

Swift Structures This project provides a framework for commonly used data structures and algorithms written in a new iOS development language called S

Wayne Bishop 2.1k Dec 28, 2022
Simple diff library in pure Swift

Diff Simple diffing library in pure Swift. Installing You can use Carthage or Swift Package Manager to install Diff. Usage Start by importing the pack

Sam Soffes 120 Sep 9, 2022
A functional tool-belt for Swift Language similar to Lo-Dash or Underscore.js in Javascript

Dollar Dollar is a Swift library that provides useful functional programming helper methods without extending any built in objects. It is similar to L

Ankur Patel 4.2k Jan 4, 2023
Swift μ-framework for efficient array diffs and datasource adapters.

Buffer Swift μ-framework for efficient array diffs, collection observation and data source implementation. C++11 port here Installation cd {PROJECT_RO

Alex Usbergo 348 Aug 2, 2022
A Graph Data Structure in Pure Swift

SwiftGraph SwiftGraph is a pure Swift (no Cocoa) implementation of a graph data structure, appropriate for use on all platforms Swift supports (iOS, m

David Kopec 700 Dec 16, 2022
A Generic Priority Queue in Pure Swift

SwiftPriorityQueue SwiftPriorityQueue is a pure Swift (no Cocoa) implementation of a generic priority queue data structure, appropriate for use on all

David Kopec 350 Dec 22, 2022
Super lightweight DB written in Swift.

Use of value types is recommended and we define standard values, simple structured data, application state and etc. as struct or enum. Pencil makes us

Naruki Chigira 88 Oct 22, 2022
A fast Swift diffing library.

HeckelDiff Pure Swift implementation of Paul Heckel's A Technique for Isolating Differences Between Files Features This is a simple diff algorithm tha

Matias Cudich 166 Oct 6, 2022
NSCoding's counterpart for Swift structs.

Dekoter Why You Might Be Interested How Much Familiar It Feels One More Example What We've Learned from It Features Save an Object to UserDefaults Arc

Artem Stepanenko 25 May 15, 2022
Algorithms and data structures in Swift, with explanations!

Welcome to the Swift Algorithm Club! Here you'll find implementations of popular algorithms and data structures in everyone's favorite new language Sw

raywenderlich 27.3k Jan 8, 2023
A Distributed Value Store in Swift.

Impeller is a Distributed Value Store (DVS) written in Swift. It was inspired by successful Distributed Version Control Systems (DVCSes) like Git and

David Coyle 1 Jun 17, 2020
🦀Amazingly incredible extraordinary lightning fast diffing in Swift

DeepDiff ❤️ Support my apps ❤️ Push Hero - pure Swift native macOS application to test push notifications PastePal - Pasteboard, note and shortcut man

Khoa 2k Dec 29, 2022
Swift library to generate differences and patches between collections.

Differ Differ generates the differences between Collection instances (this includes Strings!). It uses a fast algorithm (O((N+M)*D)) to do this. Featu

Tony Arnold 628 Dec 29, 2022
A Swift probability and statistics library

Probably Probably is a set of Swift structures for computing the probability and cumulative distributions of different probablistic functions. Right n

Harlan Haskins 270 Dec 2, 2022
The simplest abstraction to synchronize local data with remote source. For iOS, wirtten in swift.

Purpose The simplest abstraction to synchronize local data with remote source. For iOS, written in swift. Overview Many applications uses remote serve

Siarhei Ladzeika 7 Mar 17, 2022
💻 A fast and flexible O(n) difference algorithm framework for Swift collection.

A fast and flexible O(n) difference algorithm framework for Swift collection. The algorithm is optimized based on the Paul Heckel's algorithm. Made wi

Ryo Aoyama 3.3k Jan 4, 2023
Simple implementations of various dynamic data structures in Swift.

SwiftDataStructures Example To run the example project, clone the repo, and run pod install from the Example directory first. Requirements Installatio

Hector Delgado 0 Oct 21, 2021