diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+# 0.3.2.0
+
+- Add `codeUnitsRowColumn` function
+
 # 0.3.1.0
 
 - Add `splitAtLine` function
diff --git a/rope-utf16-splay.cabal b/rope-utf16-splay.cabal
--- a/rope-utf16-splay.cabal
+++ b/rope-utf16-splay.cabal
@@ -1,5 +1,5 @@
 name:                rope-utf16-splay
-version:             0.3.1.0
+version:             0.3.2.0
 synopsis:            Ropes optimised for updating using UTF-16 code units and
                      row/column pairs.
 description:         Ropes optimised for updating using UTF-16 code units and
diff --git a/src/Data/Rope/UTF16.hs b/src/Data/Rope/UTF16.hs
--- a/src/Data/Rope/UTF16.hs
+++ b/src/Data/Rope/UTF16.hs
@@ -28,6 +28,7 @@
   , Rope.drop
   , Position.RowColumn(..)
   , Rope.rowColumnCodeUnits
+  , Rope.codeUnitsRowColumn
   , Rope.splitAtLine
 
   -- * Breaking by predicate
diff --git a/src/Data/Rope/UTF16/Internal.hs b/src/Data/Rope/UTF16/Internal.hs
--- a/src/Data/Rope/UTF16/Internal.hs
+++ b/src/Data/Rope/UTF16/Internal.hs
@@ -15,7 +15,7 @@
 
 import Data.Rope.UTF16.Internal.Position
 import Data.Rope.UTF16.Internal.Text
-import Data.SplayTree(SplayTree)
+import Data.SplayTree(SplayTree, measure)
 import qualified Data.SplayTree as SplayTree
 
 data Chunk = Chunk { chunkText :: !Text, chunkMeasure :: !Position }
@@ -205,6 +205,14 @@
           Unsafe.Iter '\n' delta -> go (i + delta) (v' <> RowColumn 1 0)
           Unsafe.Iter _ 2 | v == v' <> RowColumn 0 1 -> codeUnits prePos + i
           Unsafe.Iter _ delta -> go (i + delta) (v' <> RowColumn 0 delta)
+
+-- | Get the 'RowColumn' position that corresponds to a code unit
+-- index in the rope
+--
+-- @since 0.3.2.0
+codeUnitsRowColumn :: Int -> Rope -> RowColumn
+codeUnitsRowColumn offset rope
+  = (rowColumn . measure) (Data.Rope.UTF16.Internal.take offset rope)
 
 -------------------------------------------------------------------------------
 -- * Lines
diff --git a/tests/Main.hs b/tests/Main.hs
--- a/tests/Main.hs
+++ b/tests/Main.hs
@@ -48,6 +48,11 @@
     let t = Text.pack $ replicate newlines '\n' ++ takeWhile (/= '\n') s
     Rope.clamp16 (newlines + i) t == Rope.rowColumnCodeUnits (Rope.RowColumn newlines i) (Rope.fromText t)
 
+  , testProperty "codeUnitsRowColumn" $ \s i -> do
+    let t = Text.pack s
+        rowColumn = Rope.codeUnitsRowColumn i (Rope.fromText t)
+    codeUnitsRowColumnSpec i t == rowColumn
+
   , testProperty "splitAtLine" $ \s i -> do
     let t = Text.pack s
         (rows1', rows2') = Rope.splitAtLine i (Rope.fromText t)
@@ -132,3 +137,13 @@
   ts -> do
     let (pres, posts) = splitAt i (fmap (<> "\n") (init ts) <> [last ts])
     (Text.concat pres, Text.concat posts)
+
+codeUnitsRowColumnSpec :: Int -> Text -> Rope.RowColumn
+codeUnitsRowColumnSpec i t = r
+  where
+    ts = Text.splitOn "\n" (Rope.take16 i t)
+    r = case ts of
+      []   -> Rope.RowColumn 0 0
+      [""] -> Rope.RowColumn 0 0
+      _ -> do
+        Rope.RowColumn (length (init ts)) (Unsafe.lengthWord16 (last ts))
