Skip to main content

zvec_rust/
collection.rs

1use std::ffi::CStr;
2use std::marker::PhantomData;
3use std::ptr;
4
5use crate::doc::Doc;
6use crate::error::{check_error, to_cstring, Error, ErrorCode, Result};
7use crate::multi_query::MultiQuery;
8use crate::query::SearchQuery;
9use crate::schema::{CollectionSchema, FieldSchema, IndexParams};
10
11/// Options for creating or opening a collection.
12pub struct CollectionOptions {
13    pub(crate) handle: *mut zvec_rust_sys::zvec_collection_options_t,
14}
15
16impl CollectionOptions {
17    /// Creates new collection options with default values.
18    pub fn new() -> Result<Self> {
19        let handle = unsafe { zvec_rust_sys::zvec_collection_options_create() };
20        if handle.is_null() {
21            return Err(Error {
22                code: ErrorCode::InternalError,
23                message: "failed to create collection options".into(),
24            });
25        }
26        Ok(CollectionOptions { handle })
27    }
28
29    /// Sets whether to enable memory mapping.
30    pub fn set_enable_mmap(&mut self, enable: bool) -> Result<()> {
31        check_error(unsafe {
32            zvec_rust_sys::zvec_collection_options_set_enable_mmap(self.handle, enable)
33        })
34    }
35
36    /// Returns whether memory mapping is enabled.
37    pub fn enable_mmap(&self) -> bool {
38        unsafe { zvec_rust_sys::zvec_collection_options_get_enable_mmap(self.handle) }
39    }
40
41    /// Sets the maximum buffer size in bytes.
42    pub fn set_max_buffer_size(&mut self, size: u64) -> Result<()> {
43        check_error(unsafe {
44            zvec_rust_sys::zvec_collection_options_set_max_buffer_size(self.handle, size as usize)
45        })
46    }
47
48    /// Returns the maximum buffer size in bytes.
49    pub fn max_buffer_size(&self) -> u64 {
50        unsafe { zvec_rust_sys::zvec_collection_options_get_max_buffer_size(self.handle) as u64 }
51    }
52
53    /// Sets whether the collection is read-only.
54    pub fn set_read_only(&mut self, read_only: bool) -> Result<()> {
55        check_error(unsafe {
56            zvec_rust_sys::zvec_collection_options_set_read_only(self.handle, read_only)
57        })
58    }
59
60    /// Returns whether the collection is read-only.
61    pub fn read_only(&self) -> bool {
62        unsafe { zvec_rust_sys::zvec_collection_options_get_read_only(self.handle) }
63    }
64}
65
66impl Drop for CollectionOptions {
67    fn drop(&mut self) {
68        if !self.handle.is_null() {
69            unsafe { zvec_rust_sys::zvec_collection_options_destroy(self.handle) };
70        }
71    }
72}
73
74/// Statistics about a single index in a collection.
75#[derive(Debug, Clone)]
76pub struct IndexStat {
77    pub name: String,
78    pub completeness: f32,
79}
80
81/// Statistics about a collection.
82#[derive(Debug, Clone)]
83pub struct CollectionStats {
84    pub doc_count: u64,
85    pub indexes: Vec<IndexStat>,
86}
87
88/// Per-document result of a write operation.
89#[derive(Debug, Clone)]
90pub struct DocWriteResult {
91    pub success: bool,
92    pub code: ErrorCode,
93    pub message: String,
94}
95
96impl DocWriteResult {
97    /// Returns `true` if this individual write succeeded.
98    pub fn is_success(&self) -> bool {
99        self.success
100    }
101}
102
103/// Result of a write operation (insert/update/upsert/delete).
104#[derive(Debug, Clone)]
105pub struct WriteResult {
106    pub success_count: u64,
107    pub error_count: u64,
108    pub results: Vec<DocWriteResult>,
109}
110
111/// A zvec collection for storing and querying vector data.
112///
113/// Collections are the primary data container in zvec. They hold documents
114/// with typed fields and support vector similarity search.
115///
116/// The collection is automatically closed when dropped.
117pub struct Collection {
118    handle: *mut zvec_rust_sys::zvec_collection_t,
119}
120
121impl Collection {
122    /// Returns the raw FFI handle.
123    ///
124    /// # Safety
125    /// The caller must not use the handle after the `Collection` is dropped.
126    pub unsafe fn as_raw(&self) -> *mut zvec_rust_sys::zvec_collection_t {
127        self.handle
128    }
129
130    /// Creates a `Collection` from a raw FFI handle.
131    ///
132    /// # Safety
133    /// The caller must ensure the handle is valid and was created by the zvec C API.
134    /// The `Collection` takes ownership and will call `zvec_collection_close` on drop.
135    pub unsafe fn from_raw(handle: *mut zvec_rust_sys::zvec_collection_t) -> Self {
136        Collection { handle }
137    }
138
139    /// Creates a new collection and opens it.
140    pub fn create_and_open(
141        path: &str,
142        schema: &CollectionSchema,
143        options: Option<&CollectionOptions>,
144    ) -> Result<Self> {
145        let c_path = to_cstring(path)?;
146        let c_options = options.map(|o| o.handle as *const _).unwrap_or(ptr::null());
147
148        let mut handle: *mut zvec_rust_sys::zvec_collection_t = ptr::null_mut();
149        check_error(unsafe {
150            zvec_rust_sys::zvec_collection_create_and_open(
151                c_path.as_ptr(),
152                schema.handle,
153                c_options,
154                &mut handle,
155            )
156        })?;
157
158        Ok(Collection { handle })
159    }
160
161    /// Opens an existing collection.
162    pub fn open(path: &str, options: Option<&CollectionOptions>) -> Result<Self> {
163        let c_path = to_cstring(path)?;
164        let c_options = options.map(|o| o.handle as *const _).unwrap_or(ptr::null());
165
166        let mut handle: *mut zvec_rust_sys::zvec_collection_t = ptr::null_mut();
167        check_error(unsafe {
168            zvec_rust_sys::zvec_collection_open(c_path.as_ptr(), c_options, &mut handle)
169        })?;
170
171        Ok(Collection { handle })
172    }
173
174    /// Flushes collection data to disk.
175    pub fn flush(&self) -> Result<()> {
176        check_error(unsafe { zvec_rust_sys::zvec_collection_flush(self.handle) })
177    }
178
179    /// Returns the collection schema.
180    pub fn schema(&self) -> Result<CollectionSchema> {
181        let mut schema_handle: *mut zvec_rust_sys::zvec_collection_schema_t = ptr::null_mut();
182        check_error(unsafe {
183            zvec_rust_sys::zvec_collection_get_schema(self.handle, &mut schema_handle)
184        })?;
185        Ok(CollectionSchema::from_owned(schema_handle))
186    }
187
188    /// Returns collection statistics.
189    pub fn stats(&self) -> Result<CollectionStats> {
190        let mut stats_handle: *mut zvec_rust_sys::zvec_collection_stats_t = ptr::null_mut();
191        check_error(unsafe {
192            zvec_rust_sys::zvec_collection_get_stats(self.handle, &mut stats_handle)
193        })?;
194
195        let doc_count = unsafe { zvec_rust_sys::zvec_collection_stats_get_doc_count(stats_handle) };
196        let index_count =
197            unsafe { zvec_rust_sys::zvec_collection_stats_get_index_count(stats_handle) };
198
199        let mut indexes = Vec::with_capacity(index_count);
200
201        for i in 0..index_count {
202            let name_ptr =
203                unsafe { zvec_rust_sys::zvec_collection_stats_get_index_name(stats_handle, i) };
204            let name = if name_ptr.is_null() {
205                String::new()
206            } else {
207                unsafe { CStr::from_ptr(name_ptr).to_string_lossy().into_owned() }
208            };
209
210            let completeness = unsafe {
211                zvec_rust_sys::zvec_collection_stats_get_index_completeness(stats_handle, i)
212            };
213            indexes.push(IndexStat { name, completeness });
214        }
215
216        unsafe { zvec_rust_sys::zvec_collection_stats_destroy(stats_handle) };
217
218        Ok(CollectionStats { doc_count, indexes })
219    }
220
221    // =========================================================================
222    // DML Operations
223    // =========================================================================
224
225    /// Inserts documents into the collection.
226    pub fn insert(&self, docs: &[&Doc]) -> Result<WriteResult> {
227        let ptrs: Vec<*const zvec_rust_sys::zvec_doc_t> =
228            docs.iter().map(|d| d.handle as *const _).collect();
229        let mut results: *mut zvec_rust_sys::zvec_write_result_t = ptr::null_mut();
230        let mut result_count: usize = 0;
231
232        check_error(unsafe {
233            zvec_rust_sys::zvec_collection_insert_with_results(
234                self.handle,
235                ptrs.as_ptr(),
236                ptrs.len(),
237                &mut results,
238                &mut result_count,
239            )
240        })?;
241
242        Ok(collect_write_results(results, result_count))
243    }
244
245    /// Updates documents in the collection.
246    pub fn update(&self, docs: &[&Doc]) -> Result<WriteResult> {
247        let ptrs: Vec<*const zvec_rust_sys::zvec_doc_t> =
248            docs.iter().map(|d| d.handle as *const _).collect();
249        let mut results: *mut zvec_rust_sys::zvec_write_result_t = ptr::null_mut();
250        let mut result_count: usize = 0;
251
252        check_error(unsafe {
253            zvec_rust_sys::zvec_collection_update_with_results(
254                self.handle,
255                ptrs.as_ptr(),
256                ptrs.len(),
257                &mut results,
258                &mut result_count,
259            )
260        })?;
261
262        Ok(collect_write_results(results, result_count))
263    }
264
265    /// Inserts or updates documents (upsert).
266    pub fn upsert(&self, docs: &[&Doc]) -> Result<WriteResult> {
267        let ptrs: Vec<*const zvec_rust_sys::zvec_doc_t> =
268            docs.iter().map(|d| d.handle as *const _).collect();
269        let mut results: *mut zvec_rust_sys::zvec_write_result_t = ptr::null_mut();
270        let mut result_count: usize = 0;
271
272        check_error(unsafe {
273            zvec_rust_sys::zvec_collection_upsert_with_results(
274                self.handle,
275                ptrs.as_ptr(),
276                ptrs.len(),
277                &mut results,
278                &mut result_count,
279            )
280        })?;
281
282        Ok(collect_write_results(results, result_count))
283    }
284
285    /// Deletes documents by primary keys.
286    pub fn delete(&self, pks: &[&str]) -> Result<WriteResult> {
287        let c_pks: Vec<_> = pks
288            .iter()
289            .map(|pk| to_cstring(pk))
290            .collect::<Result<Vec<_>>>()?;
291        let c_ptrs: Vec<_> = c_pks.iter().map(|pk| pk.as_ptr()).collect();
292        let mut results: *mut zvec_rust_sys::zvec_write_result_t = ptr::null_mut();
293        let mut result_count: usize = 0;
294
295        check_error(unsafe {
296            zvec_rust_sys::zvec_collection_delete_with_results(
297                self.handle,
298                c_ptrs.as_ptr(),
299                c_ptrs.len(),
300                &mut results,
301                &mut result_count,
302            )
303        })?;
304
305        Ok(collect_write_results(results, result_count))
306    }
307
308    /// Deletes documents matching a filter expression.
309    pub fn delete_by_filter(&self, filter: &str) -> Result<()> {
310        let c_filter = to_cstring(filter)?;
311        check_error(unsafe {
312            zvec_rust_sys::zvec_collection_delete_by_filter(self.handle, c_filter.as_ptr())
313        })
314    }
315
316    // =========================================================================
317    // DQL Operations
318    // =========================================================================
319
320    /// Performs a vector similarity search.
321    pub fn query(&self, query: &SearchQuery) -> Result<Vec<Doc>> {
322        let mut results: *mut *mut zvec_rust_sys::zvec_doc_t = ptr::null_mut();
323        let mut result_count: usize = 0;
324
325        check_error(unsafe {
326            zvec_rust_sys::zvec_collection_query(
327                self.handle,
328                query.handle,
329                &mut results,
330                &mut result_count,
331            )
332        })?;
333
334        let docs = unsafe { collect_docs(results, result_count) };
335        Ok(docs)
336    }
337
338    /// Performs a multi-query that combines several sub-queries with a rerank
339    /// strategy (RRF or weighted).
340    pub fn multi_query(&self, query: &MultiQuery) -> Result<Vec<Doc>> {
341        let mut results: *mut *mut zvec_rust_sys::zvec_doc_t = ptr::null_mut();
342        let mut result_count: usize = 0;
343
344        check_error(unsafe {
345            zvec_rust_sys::zvec_collection_multi_query(
346                self.handle,
347                query.handle,
348                &mut results,
349                &mut result_count,
350            )
351        })?;
352
353        let docs = unsafe { collect_docs(results, result_count) };
354        Ok(docs)
355    }
356
357    /// Fetches documents by primary keys, returning all fields including vectors.
358    pub fn fetch(&self, pks: &[&str]) -> Result<Vec<Doc>> {
359        self.fetch_with_options(pks, None, true)
360    }
361
362    /// Fetches documents by primary keys with control over which fields to return.
363    pub fn fetch_with_options(
364        &self,
365        pks: &[&str],
366        output_fields: Option<&[&str]>,
367        include_vector: bool,
368    ) -> Result<Vec<Doc>> {
369        let c_pks: Vec<_> = pks
370            .iter()
371            .map(|pk| to_cstring(pk))
372            .collect::<Result<Vec<_>>>()?;
373        let c_pk_ptrs: Vec<_> = c_pks.iter().map(|pk| pk.as_ptr()).collect();
374
375        let c_fields = output_fields
376            .map(|fields| {
377                fields
378                    .iter()
379                    .map(|f| to_cstring(f))
380                    .collect::<Result<Vec<_>>>()
381            })
382            .transpose()?;
383        let c_field_ptrs: Option<Vec<_>> = c_fields
384            .as_ref()
385            .map(|f| f.iter().map(|s| s.as_ptr()).collect());
386        let (fields_ptr, fields_count) = match &c_field_ptrs {
387            Some(ptrs) => (ptrs.as_ptr(), ptrs.len()),
388            None => (ptr::null(), 0),
389        };
390
391        let mut documents: *mut *mut zvec_rust_sys::zvec_doc_t = ptr::null_mut();
392        let mut found_count: usize = 0;
393
394        check_error(unsafe {
395            zvec_rust_sys::zvec_collection_fetch(
396                self.handle,
397                c_pk_ptrs.as_ptr(),
398                c_pk_ptrs.len(),
399                fields_ptr,
400                fields_count,
401                include_vector,
402                &mut documents,
403                &mut found_count,
404            )
405        })?;
406
407        let docs = unsafe { collect_docs(documents, found_count) };
408        Ok(docs)
409    }
410
411    // =========================================================================
412    // Document Iteration
413    // =========================================================================
414
415    /// Creates an iterator over all documents in the collection using default
416    /// options (all scalar fields, vectors included).
417    ///
418    /// The iterator traverses an isolated snapshot taken at creation time;
419    /// documents written afterwards are not visible.
420    pub fn iter(&self) -> Result<DocIterator<'_>> {
421        self.iter_with_options(None, true)
422    }
423
424    /// Creates an iterator over all documents with control over which fields
425    /// to return.
426    ///
427    /// - `output_fields`: scalar fields to return; `None` returns all fields.
428    ///   An empty slice returns no scalar fields (primary key only).
429    /// - `include_vector`: whether to include vector fields.
430    ///
431    /// The iterator traverses an isolated snapshot taken at creation time;
432    /// documents written afterwards are not visible. While the iterator is
433    /// open, schema changes (create/drop index, add/alter/drop column) and
434    /// destroy are rejected by the C library.
435    pub fn iter_with_options(
436        &self,
437        output_fields: Option<&[&str]>,
438        include_vector: bool,
439    ) -> Result<DocIterator<'_>> {
440        let options = unsafe { zvec_rust_sys::zvec_iterator_options_create() };
441        if options.is_null() {
442            return Err(Error {
443                code: ErrorCode::InternalError,
444                message: "failed to create iterator options".into(),
445            });
446        }
447
448        // Build C string fields before the first FFI call that must be cleaned
449        // up on failure; `options` is destroyed on every early return.
450        let result = (|| -> Result<()> {
451            let c_fields = output_fields
452                .map(|fields| {
453                    fields
454                        .iter()
455                        .map(|f| to_cstring(f))
456                        .collect::<Result<Vec<_>>>()
457                })
458                .transpose()?;
459            let c_field_ptrs: Option<Vec<_>> = c_fields
460                .as_ref()
461                .map(|f| f.iter().map(|s| s.as_ptr()).collect());
462            let (fields_ptr, fields_count) = match &c_field_ptrs {
463                Some(ptrs) => (ptrs.as_ptr(), ptrs.len()),
464                None => (ptr::null(), 0),
465            };
466
467            check_error(unsafe {
468                zvec_rust_sys::zvec_iterator_options_set_output_fields(
469                    options,
470                    fields_ptr,
471                    fields_count,
472                )
473            })?;
474            check_error(unsafe {
475                zvec_rust_sys::zvec_iterator_options_set_include_vector(options, include_vector)
476            })?;
477            Ok(())
478        })();
479        if let Err(e) = result {
480            unsafe { zvec_rust_sys::zvec_iterator_options_destroy(options) };
481            return Err(e);
482        }
483
484        let mut iter: *mut zvec_rust_sys::zvec_doc_iterator_t = ptr::null_mut();
485        let rc = check_error(unsafe {
486            zvec_rust_sys::zvec_collection_create_iterator(self.handle, options, &mut iter)
487        });
488        unsafe { zvec_rust_sys::zvec_iterator_options_destroy(options) };
489        rc?;
490
491        Ok(DocIterator {
492            handle: iter,
493            _collection: PhantomData,
494        })
495    }
496
497    // =========================================================================
498    // Index Management
499    // =========================================================================
500
501    /// Creates an index on a field.
502    pub fn create_index(&self, field_name: &str, params: &IndexParams) -> Result<()> {
503        let c_name = to_cstring(field_name)?;
504        check_error(unsafe {
505            zvec_rust_sys::zvec_collection_create_index(self.handle, c_name.as_ptr(), params.handle)
506        })
507    }
508
509    /// Drops an index from a field.
510    pub fn drop_index(&self, field_name: &str) -> Result<()> {
511        let c_name = to_cstring(field_name)?;
512        check_error(unsafe {
513            zvec_rust_sys::zvec_collection_drop_index(self.handle, c_name.as_ptr())
514        })
515    }
516
517    /// Optimizes the collection (rebuild indexes, merge segments, etc.).
518    pub fn optimize(&self) -> Result<()> {
519        check_error(unsafe { zvec_rust_sys::zvec_collection_optimize(self.handle) })
520    }
521
522    // =========================================================================
523    // DDL Operations
524    // =========================================================================
525
526    /// Adds a column to the collection.
527    pub fn add_column(&self, field_schema: &FieldSchema, default_expr: Option<&str>) -> Result<()> {
528        let c_expr_owned = default_expr.map(to_cstring).transpose()?;
529        let c_expr_ptr = c_expr_owned
530            .as_ref()
531            .map(|s| s.as_ptr())
532            .unwrap_or(ptr::null());
533        check_error(unsafe {
534            zvec_rust_sys::zvec_collection_add_column(self.handle, field_schema.handle, c_expr_ptr)
535        })
536    }
537
538    /// Drops a column from the collection.
539    pub fn drop_column(&self, name: &str) -> Result<()> {
540        let c_name = to_cstring(name)?;
541        check_error(unsafe {
542            zvec_rust_sys::zvec_collection_drop_column(self.handle, c_name.as_ptr())
543        })
544    }
545
546    /// Closes the collection explicitly.
547    pub fn close(self) -> Result<()> {
548        // Drop will handle the close
549        drop(self);
550        Ok(())
551    }
552}
553
554impl Drop for Collection {
555    fn drop(&mut self) {
556        if !self.handle.is_null() {
557            // Safety: handle was created by zvec_collection_create_and_open or zvec_collection_open
558            let rc = unsafe { zvec_rust_sys::zvec_collection_close(self.handle) };
559            if rc != zvec_rust_sys::ZVEC_OK {
560                eprintln!(
561                    "zvec warning: failed to close collection (error code {})",
562                    rc
563                );
564            }
565        }
566    }
567}
568
569// Safety: The zvec C-API documents that collection operations are internally
570// thread-safe. All mutable state is protected by internal locks in the C library.
571// See: https://github.com/alibaba/zvec — C-API thread-safety guarantees.
572unsafe impl Send for Collection {}
573unsafe impl Sync for Collection {}
574
575/// Iterator over all documents in a collection.
576///
577/// Created by [`Collection::iter`] or [`Collection::iter_with_options`].
578/// Traverses an isolated snapshot taken at creation time. Each `next` call
579/// returns `Some(Ok(doc))` for a document, `Some(Err(e))` on failure, and
580/// `None` at the end of the collection.
581///
582/// The iterator borrows its collection, so the collection cannot be dropped
583/// while the iterator is alive. Dropping the iterator closes it via
584/// `zvec_doc_iterator_close`.
585pub struct DocIterator<'a> {
586    handle: *mut zvec_rust_sys::zvec_doc_iterator_t,
587    _collection: PhantomData<&'a Collection>,
588}
589
590impl<'a> Iterator for DocIterator<'a> {
591    type Item = Result<Doc>;
592
593    fn next(&mut self) -> Option<Self::Item> {
594        let mut doc: *mut zvec_rust_sys::zvec_doc_t = ptr::null_mut();
595        let rc = unsafe { zvec_rust_sys::zvec_doc_iterator_next(self.handle, &mut doc) };
596        if rc != zvec_rust_sys::ZVEC_OK {
597            return Some(Err(Error {
598                code: ErrorCode::from(rc),
599                message: format!("document iterator failed with error code {}", rc),
600            }));
601        }
602        if doc.is_null() {
603            return None; // EOF
604        }
605        // Take ownership: Rust will call zvec_doc_destroy on drop.
606        Some(Ok(unsafe { Doc::from_raw(doc) }))
607    }
608}
609
610impl<'a> Drop for DocIterator<'a> {
611    fn drop(&mut self) {
612        if !self.handle.is_null() {
613            unsafe { zvec_rust_sys::zvec_doc_iterator_close(self.handle) };
614        }
615    }
616}
617
618/// Parses a C array of `zvec_write_result_t` into a `WriteResult`.
619fn collect_write_results(
620    results: *mut zvec_rust_sys::zvec_write_result_t,
621    count: usize,
622) -> WriteResult {
623    let mut doc_results = Vec::with_capacity(count);
624    let mut success_count: u64 = 0;
625    let mut error_count: u64 = 0;
626
627    if !results.is_null() && count > 0 {
628        for i in 0..count {
629            let wr = unsafe { &*results.add(i) };
630            let is_ok = wr.code == zvec_rust_sys::ZVEC_OK;
631            let message = if wr.message.is_null() {
632                String::new()
633            } else {
634                unsafe { CStr::from_ptr(wr.message).to_string_lossy().into_owned() }
635            };
636            if is_ok {
637                success_count += 1;
638            } else {
639                error_count += 1;
640            }
641            doc_results.push(DocWriteResult {
642                success: is_ok,
643                code: ErrorCode::from(wr.code),
644                message,
645            });
646        }
647        unsafe { zvec_rust_sys::zvec_write_results_free(results, count) };
648    }
649
650    WriteResult {
651        success_count,
652        error_count,
653        results: doc_results,
654    }
655}
656
657/// Collects document pointers from a C array into a Vec<Doc>.
658///
659/// # Safety
660/// `results` must point to a valid array of `count` document pointers
661/// allocated by the zvec C library.
662unsafe fn collect_docs(results: *mut *mut zvec_rust_sys::zvec_doc_t, count: usize) -> Vec<Doc> {
663    if results.is_null() || count == 0 {
664        return Vec::new();
665    }
666
667    let mut docs = Vec::with_capacity(count);
668    for i in 0..count {
669        let doc_ptr = *results.add(i);
670        if !doc_ptr.is_null() {
671            // Take ownership: Rust will call zvec_doc_destroy on drop
672            docs.push(Doc::from_raw(doc_ptr));
673        }
674    }
675
676    // Free only the pointer array itself (not the individual docs, which are now
677    // owned by the Doc wrappers above and will be freed via their Drop impls).
678    zvec_rust_sys::zvec_free(results as *mut std::os::raw::c_void);
679
680    docs
681}
682
683#[cfg(test)]
684mod tests {
685    use super::*;
686
687    #[test]
688    fn collect_docs_handles_null_pointer() {
689        let docs = unsafe { collect_docs(ptr::null_mut(), 0) };
690        assert!(docs.is_empty());
691    }
692
693    #[test]
694    fn collect_docs_handles_zero_count() {
695        // Even with a non-null pointer, zero count should return empty
696        let mut fake: *mut zvec_rust_sys::zvec_doc_t = ptr::null_mut();
697        let docs = unsafe { collect_docs(&mut fake as *mut _, 0) };
698        assert!(docs.is_empty());
699    }
700}