https://github.com/ClickHouse/ClickHouse/blob/9a48cf0da8badfc5f3da08611eb44961aa28e3f6/src/Functions/FunctionsHashing.h#L669-L671

There's a call to `blake3_free_char_pointer` that passes ownership of the pointer back to Rust (which then frees the memory), then there's a call to `std::string`'s constructor that accepts that now-freed pointer.
{
        using ColVecType = ColumnVectorOrDecimal<FromType>;
        if (const ColVecType * col_from = checkAndGetColumn<ColVecType>(arguments[0].column.get()))
        {
            auto col_to = ColumnVector<ToType>::create();
            const typename ColVecType::Container & vec_from = col_from->getData();
            typename ColumnVector<ToType>::Container & vec_to = col_to->getData();
            size_t size = vec_from.size();
            vec_to.resize(size);
            for (size_t i = 0; i < size; ++i)
                vec_to[i] = Impl::apply(vec_from[i]);
            return col_to;
        }
        else
            throw Exception("Illegal column " + arguments[0].column->getName()
                    + " of first argument of function " + Name::name,
                ErrorCodes::ILLEGAL_COLUMN);
    }
public:
    String getName() const override
    {
        return name;
    }
    size_t getNumberOfArguments() const override { return 1; }
    DataTypePtr getReturnTypeImpl(const DataTypes & arguments) const override