diff --git a/docs/src/api.md b/docs/src/api.md
index fecb4b7..e34de90 100644
--- a/docs/src/api.md
+++ b/docs/src/api.md
@@ -5,6 +5,7 @@
 ```@docs
 inverse
 NoInverse
+setinverse
 ```
 
 ## Test utility
@@ -13,8 +14,9 @@ NoInverse
 InverseFunctions.test_inverse
 ```
 
-## Additional functions
+## Additional functionality
 
 ```@docs
 InverseFunctions.square
+InverseFunctions.FunctionWithInverse
 ```
diff --git a/src/InverseFunctions.jl b/src/InverseFunctions.jl
index c22608e..0c5438b 100644
--- a/src/InverseFunctions.jl
+++ b/src/InverseFunctions.jl
@@ -10,6 +10,7 @@ using Test
 
 include("functions.jl")
 include("inverse.jl")
+include("setinverse.jl")
 include("test.jl")
 
 end # module
diff --git a/src/setinverse.jl b/src/setinverse.jl
new file mode 100644
index 0000000..b027c53
--- /dev/null
+++ b/src/setinverse.jl
@@ -0,0 +1,54 @@
+# This file is a part of InverseFunctions.jl, licensed under the MIT License (MIT).
+
+
+"""
+    struct FunctionWithInverse{F,InvF} <: Function
+
+A function with an inverse.
+
+Do not construct directly, use [`setinverse(f, invf)`](@ref) instead.
+"""
+struct FunctionWithInverse{F,InvF} <: Function
+    f::F
+    invf::InvF
+end
+
+
+(f::FunctionWithInverse)(x) = f.f(x)
+
+inverse(f::FunctionWithInverse) = setinverse(f.invf, f.f)
+
+
+"""
+    setinverse(f, invf)
+
+Return a function that behaves like `f` and uses `invf` as its inverse.
+
+Useful in cases where no inverse is defined for `f` or to set an inverse that
+is only valid within a given context, e.g. only for a limited argument
+range that is guaranteed by the use case but not in general.
+
+For example, `asin` is not a valid inverse of `sin` for arbitrary arguments
+of `sin`, but can be a valid inverse if the use case guarantees that the
+argument of `sin` will always be within `-π` and `π`:
+
+```jldoctest
+julia> foo = setinverse(sin, asin);
+
+julia> x = π/3;
+
+julia> foo(x) == sin(x)
+true
+
+julia> inverse(foo)(foo(x)) ≈ x
+true
+
+julia> inverse(foo) === setinverse(asin, sin)
+true
+```
+"""
+setinverse(f, invf) = FunctionWithInverse(_unwrap_f(f), _unwrap_f(invf))
+export setinverse
+
+_unwrap_f(f) = f
+_unwrap_f(f::FunctionWithInverse) = f.f
diff --git a/test/runtests.jl b/test/runtests.jl
index b076cff..58ee692 100644
--- a/test/runtests.jl
+++ b/test/runtests.jl
@@ -7,6 +7,7 @@ import Documenter
 Test.@testset "Package InverseFunctions" begin
     include("test_functions.jl")
     include("test_inverse.jl")
+    include("test_setinverse.jl")
 
     # doctests
     Documenter.DocMeta.setdocmeta!(
diff --git a/test/test_setinverse.jl b/test/test_setinverse.jl
new file mode 100644
index 0000000..50b97ef
--- /dev/null
+++ b/test/test_setinverse.jl
@@ -0,0 +1,15 @@
+# This file is a part of InverseFunctions.jl, licensed under the MIT License (MIT).
+
+using Test
+using InverseFunctions
+
+
+@testset "setinverse" begin
+    @test @inferred(setinverse(sin, asin)) === InverseFunctions.FunctionWithInverse(sin, asin)
+    @test @inferred(setinverse(sin, setinverse(asin, sqrt))) === InverseFunctions.FunctionWithInverse(sin, asin)
+    @test @inferred(setinverse(setinverse(sin, sqrt), asin)) === InverseFunctions.FunctionWithInverse(sin, asin)
+    @test @inferred(setinverse(setinverse(sin, asin), setinverse(asin, sqrt))) === InverseFunctions.FunctionWithInverse(sin, asin)
+
+    InverseFunctions.test_inverse(setinverse(sin, asin), π/4)
+    InverseFunctions.test_inverse(setinverse(asin, sin), 0.5)
+end