diff --git a/Data/Conduit.hs b/Data/Conduit.hs
--- a/Data/Conduit.hs
+++ b/Data/Conduit.hs
@@ -24,6 +24,7 @@
     , await
     , yield
     , leftover
+    , runConduit
 
       -- ** Finalization
     , bracketP
diff --git a/Data/Conduit/Internal/Conduit.hs b/Data/Conduit/Internal/Conduit.hs
--- a/Data/Conduit/Internal/Conduit.hs
+++ b/Data/Conduit/Internal/Conduit.hs
@@ -30,6 +30,7 @@
     , yieldM
     , yieldOr
     , leftover
+    , runConduit
       -- ** Composition
     , connectResume
     , connectResumeConduit
@@ -829,6 +830,13 @@
 leftover :: i -> ConduitM i o m ()
 leftover i = ConduitM $ \rest -> Leftover (rest ()) i
 {-# INLINE leftover #-}
+
+-- | Run a pipeline until processing completes.
+--
+-- Since 1.2.1
+runConduit :: Monad m => ConduitM () Void m r -> m r
+runConduit (ConduitM p) = runPipe $ injectLeftovers $ p Done
+{-# INLINE [0] runConduit #-}
 
 -- | Perform some allocation and run an inner component. Two guarantees are
 -- given about resource finalization:
diff --git a/Data/Conduit/Internal/Fusion.hs b/Data/Conduit/Internal/Fusion.hs
--- a/Data/Conduit/Internal/Fusion.hs
+++ b/Data/Conduit/Internal/Fusion.hs
@@ -51,6 +51,28 @@
         unstream left =$= unstream right = unstream (fuseStream left right)
   #-}
 
+runStream :: Monad m
+          => StreamConduit () Void m r
+          -> m r
+runStream (StreamConduit _ f) =
+    run $ f $ Stream emptyStep (return ())
+  where
+    emptyStep _ = return $ Stop ()
+    run (Stream step ms0) =
+        ms0 >>= loop
+      where
+        loop s = do
+            res <- step s
+            case res of
+                Stop r -> return r
+                Skip s' -> loop s'
+                Emit _ o -> absurd o
+{-# INLINE runStream #-}
+
+{-# RULES "runStream" forall stream.
+        runConduit (unstream stream) = runStream stream
+  #-}
+
 connectStream :: Monad m
               => StreamConduit () i    m ()
               -> StreamConduit i  Void m r
diff --git a/benchmarks/unfused.hs b/benchmarks/unfused.hs
--- a/benchmarks/unfused.hs
+++ b/benchmarks/unfused.hs
@@ -42,6 +42,13 @@
            $$ CL.map (* 2)
            =$ CL.map (+ 1)
            =$ CL.fold (+) 0
+    , bench "runConduit, completely fused" $ flip whnf upper0 $ \upper ->
+        runIdentity
+            $ runConduit
+            $ CL.enumFromTo 1 upper
+          =$= CL.map (* 2)
+          =$= CL.map (+ 1)
+          =$= CL.fold (+) 0
     , bench "completely unfused" $ flip whnf upper0 $ \upper ->
         runIdentity
             $ enumFromToC 1 upper
diff --git a/conduit.cabal b/conduit.cabal
--- a/conduit.cabal
+++ b/conduit.cabal
@@ -1,5 +1,5 @@
 Name:                conduit
-Version:             1.2.0.2
+Version:             1.2.1
 Synopsis:            Streaming data processing library.
 Description:
     @conduit@ is a solution to the streaming data problem, allowing for production, transformation, and consumption of streams of data in constant memory. It is an alternative to lazy I\/O which guarantees deterministic resource handling, and fits in the same general solution space as @enumerator@\/@iteratee@ and @pipes@. For a tutorial, please visit <https://haskell.fpcomplete.com/user/snoyberg/library-documentation/conduit-overview>.
