quickwit-oss/tantivy · error

FastFieldsPlugin is a built-in; use FastFieldsPluginWriter::

Error message

FastFieldsPlugin is a built-in; use FastFieldsPluginWriter::new

What it means

FastFieldsPlugin implements SegmentPlugin, but it is a built-in whose fast-field files are written by the segment writer itself. Its SegmentPlugin::create_writer deliberately panics with unimplemented!(); callers must construct FastFieldsPluginWriter directly instead of asking the plugin to create a writer.

Source

Thrown at src/fastfield/plugin.rs:34

use crate::fastfield::{FastFieldReaders, FastFieldsWriter};
use crate::index::{SegmentComponent, SegmentReader};
use crate::indexer::doc_id_mapping::{DocIdMapping, MappingType, SegmentDocIdMapping};
use crate::plugin::{PluginMergeContext, PluginWriter, PluginWriterContext, SegmentPlugin};
use crate::schema::document::Document;
use crate::schema::{value_type_to_column_type, Schema};
use crate::space_usage::{ComponentSpaceUsage, FAST_FIELDS};
use crate::Segment;

/// Built-in segment plugin that stores and merges fast-field columns.
pub struct FastFieldsPlugin;

impl SegmentPlugin for FastFieldsPlugin {
    fn extensions(&self) -> &[&str] {
        &["fast"]
    }

    fn create_writer(&self, _ctx: &PluginWriterContext) -> crate::Result<Box<dyn PluginWriter>> {
        unimplemented!("FastFieldsPlugin is a built-in; use FastFieldsPluginWriter::new")
    }

    fn merge(&self, ctx: PluginMergeContext) -> crate::Result<()> {
        debug_time!("write-fast-fields");
        let path = ctx
            .target_segment
            .relative_path(SegmentComponent::FastFields);
        let mut fast_field_wrt: WritePtr =
            ctx.target_segment.index().directory().open_write(&path)?;

        let required_columns = extract_fast_field_required_columns(ctx.schema);
        let columnars: Vec<&ColumnarReader> = ctx
            .readers
            .iter()
            .map(|reader| reader.fast_fields().columnar())
            .collect();

        // Clone the doc_id_mapping since convert_to_merge_order consumes it by value

View on GitHub (pinned to b5d8deb80c)

Solutions

  1. Use FastFieldsPluginWriter::new directly to create the fast-field writer
  2. Exclude FastFieldsPlugin from generic create_writer dispatch; special-case it (fast fields are handled internally during segment writing)
  3. Remove the plugin from any list of plugins expected to provide writers

Example fix

// before
let writer = fast_fields_plugin.create_writer(ctx)?;
// after
let writer = FastFieldsPluginWriter::new(/* ... */);
Defensive patterns

Strategy: type-guard

Validate before calling

// only call create_writer on non-builtin plugins
if std::any::type_name::<P>() != "tantivy::fastfield::plugin::FastFieldsPlugin" {
    let writer = plugin.create_writer(ctx)?;
}

Type guard

fn builtin_fast_fields_plugin(p: &dyn SegmentPlugin) -> bool {
    p.extensions() == &["fast"] && std::any::type_name_of_val(p).contains("FastFieldsPlugin")
}

Try / catch

let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| plugin.create_writer(ctx)));
match result {
    Ok(w) => use_writer(w),
    Err(_) => eprintln!("builtin plugin: use FastFieldsPluginWriter::new instead"),
}

Prevention

When it happens

Trigger: Calling FastFieldsPlugin.create_writer(ctx) — e.g. registering the plugin in a generic plugin pipeline that invokes create_writer for every plugin, or calling it manually.

Common situations: Misconfigured plugin registry treating the built-in plugin like an external one; custom segment-writer code iterating plugins and calling create_writer unconditionally.

Related errors


AI-assisted analysis of quickwit-oss/tantivy@b5d8deb80c (2026-09-05). Data as JSON: /api/errors/1f88cd186e212b3d. Report an issue: GitHub.