diff --git a/include/qemu/main-loop.h b/include/qemu/main-loop.h
index 646306c272f..3935a573391 100644
--- a/include/qemu/main-loop.h
+++ b/include/qemu/main-loop.h
@@ -247,6 +247,14 @@ void event_notifier_set_handler(EventNotifier *e,
 GSource *iohandler_get_g_source(void);
 AioContext *iohandler_get_aio_context(void);
 
+/**
+ * rust_bql_mock_lock:
+ *
+ * Called from Rust doctests to make bql_lock() return true.
+ * Do not touch.
+ */
+void rust_bql_mock_lock(void);
+
 /**
  * bql_locked: Return lock status of the Big QEMU Lock (BQL)
  *
diff --git a/stubs/iothread-lock.c b/stubs/iothread-lock.c
index 54676598950..6050c081f53 100644
--- a/stubs/iothread-lock.c
+++ b/stubs/iothread-lock.c
@@ -1,11 +1,17 @@
 #include "qemu/osdep.h"
 #include "qemu/main-loop.h"
 
+static bool bql_is_locked = false;
 static uint32_t bql_unlock_blocked;
 
 bool bql_locked(void)
 {
-    return false;
+    return bql_is_locked;
+}
+
+void rust_bql_mock_lock(void)
+{
+    bql_is_locked = true;
 }
 
 void bql_lock_impl(const char *file, int line)
diff --git a/rust/qemu-api/meson.build b/rust/qemu-api/meson.build
index 7ff408ad68e..50ec00e128d 100644
--- a/rust/qemu-api/meson.build
+++ b/rust/qemu-api/meson.build
@@ -60,7 +60,7 @@ test('rust-qemu-api-integration',
         dependencies: [qemu_api, qemu_api_macros],
         link_whole: [rust_qemu_api_objs, libqemuutil]),
     args: [
-        '--test',
+        '--test', '--test-threads', '1',
         '--format', 'pretty',
     ],
     protocol: 'rust',
diff --git a/rust/qemu-api/src/cell.rs b/rust/qemu-api/src/cell.rs
index 28349de291a..eae4e2ce786 100644
--- a/rust/qemu-api/src/cell.rs
+++ b/rust/qemu-api/src/cell.rs
@@ -124,9 +124,18 @@
 
 use crate::bindings;
 
-// TODO: When building doctests do not include the actual BQL, because cargo
-// does not know how to link them to libqemuutil.  This can be fixed by
-// running rustdoc from "meson test" instead of relying on cargo.
+/// An internal function that is used by doctests.
+pub fn bql_start_test() {
+    if cfg!(MESON) {
+        // SAFETY: integration tests are run with --test-threads=1, while
+        // unit tests and doctests are not multithreaded and do not have
+        // any BQL-protected data.  Just set bql_locked to true.
+        unsafe {
+            bindings::rust_bql_mock_lock();
+        }
+    }
+}
+
 pub fn bql_locked() -> bool {
     // SAFETY: the function does nothing but return a thread-local bool
     !cfg!(MESON) || unsafe { bindings::bql_locked() }
@@ -220,6 +229,7 @@ impl<T> BqlCell<T> {
     ///
     /// ```
     /// use qemu_api::cell::BqlCell;
+    /// # qemu_api::cell::bql_start_test();
     ///
     /// let c = BqlCell::new(5);
     /// ```
@@ -236,6 +246,7 @@ pub const fn new(value: T) -> BqlCell<T> {
     ///
     /// ```
     /// use qemu_api::cell::BqlCell;
+    /// # qemu_api::cell::bql_start_test();
     ///
     /// let c = BqlCell::new(5);
     ///
@@ -253,6 +264,7 @@ pub fn set(&self, val: T) {
     ///
     /// ```
     /// use qemu_api::cell::BqlCell;
+    /// # qemu_api::cell::bql_start_test();
     ///
     /// let cell = BqlCell::new(5);
     /// assert_eq!(cell.get(), 5);
@@ -274,6 +286,7 @@ pub fn replace(&self, val: T) -> T {
     ///
     /// ```
     /// use qemu_api::cell::BqlCell;
+    /// # qemu_api::cell::bql_start_test();
     ///
     /// let c = BqlCell::new(5);
     /// let five = c.into_inner();
@@ -293,6 +306,7 @@ impl<T: Copy> BqlCell<T> {
     ///
     /// ```
     /// use qemu_api::cell::BqlCell;
+    /// # qemu_api::cell::bql_start_test();
     ///
     /// let c = BqlCell::new(5);
     ///
@@ -315,6 +329,7 @@ impl<T> BqlCell<T> {
     ///
     /// ```
     /// use qemu_api::cell::BqlCell;
+    /// # qemu_api::cell::bql_start_test();
     ///
     /// let c = BqlCell::new(5);
     ///
@@ -333,6 +348,7 @@ impl<T: Default> BqlCell<T> {
     ///
     /// ```
     /// use qemu_api::cell::BqlCell;
+    /// # qemu_api::cell::bql_start_test();
     ///
     /// let c = BqlCell::new(5);
     /// let five = c.take();
@@ -461,6 +477,7 @@ fn panic_already_borrowed(&self) -> ! {
     ///
     /// ```
     /// use qemu_api::cell::BqlRefCell;
+    /// # qemu_api::cell::bql_start_test();
     ///
     /// let c = BqlRefCell::new(5);
     ///
@@ -472,6 +489,7 @@ fn panic_already_borrowed(&self) -> ! {
     ///
     /// ```should_panic
     /// use qemu_api::cell::BqlRefCell;
+    /// # qemu_api::cell::bql_start_test();
     ///
     /// let c = BqlRefCell::new(5);
     ///
@@ -513,6 +531,7 @@ pub fn borrow(&self) -> BqlRef<'_, T> {
     ///
     /// ```
     /// use qemu_api::cell::BqlRefCell;
+    /// # qemu_api::cell::bql_start_test();
     ///
     /// let c = BqlRefCell::new("hello".to_owned());
     ///
@@ -525,6 +544,7 @@ pub fn borrow(&self) -> BqlRef<'_, T> {
     ///
     /// ```should_panic
     /// use qemu_api::cell::BqlRefCell;
+    /// # qemu_api::cell::bql_start_test();
     ///
     /// let c = BqlRefCell::new(5);
     /// let m = c.borrow();
