Here is an ugly approach i use sometimes ...
(and I reuse your rfunc idea)
rf[True, Missing["Unmatched"]] := prev;
rf[_, b_] := prev = b;
previd = "";
prev = -999;
ds3[All, (<|#, val = rf[#ID == previd, #B]; previd = #ID;
"B" -> val|>) &]
Or try this not much more satisfying approach (the idea is to extract/work on the column then replace it in the dataset) :
foo[b_, Missing["Unmatched"]] := b;
foo[_, c_] := c;
ds3[All, {"ID", "B"}] // Values // Normal //
GroupBy[#, First -> Last, FoldList[foo]] & // Values //
Flatten // Map[<|"B" -> #|> &, #] & //
Join @@@ Transpose[{Normal@ds3, #}] & // Dataset