diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,10 @@
 # Revision history for byteslice
 
+## 0.2.5.0 -- 2021-01-22
+
+* Add `Data.Bytes.Chunks.concatByteString`.
+* Expose `pinnedToByteString` to end users. 
+
 ## 0.2.4.0 -- 2020-10-15
 
 * Add `toByteString` and `fromByteString`.
diff --git a/byteslice.cabal b/byteslice.cabal
--- a/byteslice.cabal
+++ b/byteslice.cabal
@@ -1,6 +1,6 @@
 cabal-version: 2.2
 name: byteslice
-version: 0.2.4.0
+version: 0.2.5.0
 synopsis: Slicing managed and unmanaged memory
 description:
   This library provides types that allow the user to talk about a slice of
diff --git a/src/Data/Bytes.hs b/src/Data/Bytes.hs
--- a/src/Data/Bytes.hs
+++ b/src/Data/Bytes.hs
@@ -121,7 +121,8 @@
   , Pure.fromByteArray
   , toLatinString
   , fromCString#
-  , toByteString
+  , Pure.toByteString
+  , Pure.pinnedToByteString
   , fromByteString
   , fromShortByteString
   , toShortByteString
@@ -153,7 +154,6 @@
 import GHC.Word (Word8(W8#))
 
 import qualified Data.ByteString as ByteString
-import qualified Data.ByteString.Internal as ByteString
 import qualified Data.ByteString.Unsafe as ByteString
 import qualified Data.Bytes.Byte as Byte
 import qualified Data.Bytes.Chunks as Chunks
@@ -165,7 +165,6 @@
 import qualified Data.Primitive as PM
 import qualified Data.Primitive.Ptr as PM
 import qualified GHC.Exts as Exts
-import qualified GHC.ForeignPtr as ForeignPtr
 
 -- | Is the byte sequence empty?
 null :: Bytes -> Bool
@@ -703,11 +702,6 @@
 {-# inline all #-}
 all f = foldr (\b r -> f b && r) True
 
--- | /O(n)/ when unpinned, /O(1)/ when pinned. Create a 'ByteString' from
--- a byte sequence. This only copies the byte sequence if it is not pinned.
-toByteString :: Bytes -> ByteString
-toByteString !b = pinnedToByteString (Pure.pin b)
-
 -- | Convert the sliced 'Bytes' to an unsliced 'ShortByteString'. This
 -- reuses the array backing the sliced 'Bytes' if the slicing metadata
 -- implies that all of the bytes are used. Otherwise, it makes a copy.
@@ -761,13 +755,3 @@
   ) 0 len
   where
   !len = ByteString.length b
-
--- Precondition: bytes are pinned
-pinnedToByteString :: Bytes -> ByteString
-pinnedToByteString (Bytes y@(PM.ByteArray x) off len) =
-  ByteString.PS
-    (ForeignPtr.ForeignPtr
-      (case plusPtr (PM.byteArrayContents y) off of {Exts.Ptr p -> p})
-      (ForeignPtr.PlainPtr (Exts.unsafeCoerce# x))
-    )
-    0 len
diff --git a/src/Data/Bytes/Chunks.hs b/src/Data/Bytes/Chunks.hs
--- a/src/Data/Bytes/Chunks.hs
+++ b/src/Data/Bytes/Chunks.hs
@@ -25,6 +25,7 @@
   , concatPinned
   , concatU
   , concatPinnedU
+  , concatByteString
   , reverse
   , reverseOnto
     -- * Folds
@@ -51,20 +52,21 @@
 import Control.Exception (IOException,catch)
 import Control.Monad.ST.Run (runIntByteArrayST)
 import Data.Bits (xor)
+import Data.ByteString (ByteString)
 import Data.Bytes.Types (Bytes(Bytes))
-import Data.Word (Word8,Word32,Word64)
 import Data.Primitive (ByteArray(..),MutableByteArray(..))
+import Data.Word (Word8,Word32,Word64)
 import GHC.Exts (ByteArray#,MutableByteArray#)
 import GHC.Exts (Int#,State#,Int(I#),(+#))
 import GHC.ST (ST(..))
 import System.IO (Handle,hFileSize,IOMode(ReadMode,WriteMode),withBinaryFile)
 
-import qualified GHC.Exts as Exts
-import qualified Data.Primitive as PM
-import qualified Data.Bytes.Types as B
-import qualified Data.Bytes.Pure as Bytes
 import qualified Data.Bytes.Byte as Byte
 import qualified Data.Bytes.IO as IO
+import qualified Data.Bytes.Pure as Bytes
+import qualified Data.Bytes.Types as B
+import qualified Data.Primitive as PM
+import qualified GHC.Exts as Exts
 
 -- | A cons-list of byte sequences.
 data Chunks
@@ -104,6 +106,10 @@
     ChunksNil -> Bytes.pin b
     ChunksCons c z -> case concatPinnedFollowing2 b c z of
       (# len, r #) -> Bytes (ByteArray r) 0 (I# len)
+
+-- | Concatenate chunks into a strict bytestring.
+concatByteString :: Chunks -> ByteString
+concatByteString c = Bytes.pinnedToByteString (concatPinned c)
 
 -- | Concatenate chunks into a single contiguous byte sequence.
 concat :: Chunks -> Bytes
diff --git a/src/Data/Bytes/Pure.hs b/src/Data/Bytes/Pure.hs
--- a/src/Data/Bytes/Pure.hs
+++ b/src/Data/Bytes/Pure.hs
@@ -21,6 +21,8 @@
   , foldl'
   , fnv1a32
   , fnv1a64
+  , toByteString
+  , pinnedToByteString
   ) where
 
 import Prelude hiding (length)
@@ -28,12 +30,16 @@
 import Control.Monad.Primitive (PrimState,PrimMonad)
 import Control.Monad.ST.Run (runByteArrayST)
 import Data.Bits (xor)
+import Data.ByteString (ByteString)
 import Data.Bytes.Types (Bytes(Bytes))
 import Data.Primitive (ByteArray,MutableByteArray)
 import Data.Word (Word64,Word32,Word8)
 import Foreign.Ptr (Ptr,plusPtr)
 
+import qualified Data.ByteString.Internal as ByteString
 import qualified Data.Primitive as PM
+import qualified GHC.Exts as Exts
+import qualified GHC.ForeignPtr as ForeignPtr
 
 -- | The empty byte sequence.
 empty :: Bytes
@@ -145,3 +151,17 @@
   PM.copyByteArray m 0 arr off len
   PM.unsafeFreezeByteArray m
 
+-- | /O(n)/ when unpinned, /O(1)/ when pinned. Create a 'ByteString' from
+-- a byte sequence. This only copies the byte sequence if it is not pinned.
+toByteString :: Bytes -> ByteString
+toByteString !b = pinnedToByteString (pin b)
+
+-- | /O(1)/ Precondition: bytes are pinned. Behavior is undefined otherwise.
+pinnedToByteString :: Bytes -> ByteString
+pinnedToByteString (Bytes y@(PM.ByteArray x) off len) =
+  ByteString.PS
+    (ForeignPtr.ForeignPtr
+      (case plusPtr (PM.byteArrayContents y) off of {Exts.Ptr p -> p})
+      (ForeignPtr.PlainPtr (Exts.unsafeCoerce# x))
+    )
+    0 len
